@zambon-dev/ui 0.0.0-stage → 0.0.1-bootstrap.0
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/CHANGELOG.md +2325 -0
- package/README.md +81 -3
- package/fesm2022/zambon-dev-ui-framework.mjs +3997 -0
- package/fesm2022/zambon-dev-ui-framework.mjs.map +1 -0
- package/fesm2022/zambon-dev-ui-library.mjs +4280 -0
- package/fesm2022/zambon-dev-ui-library.mjs.map +1 -0
- package/fesm2022/zambon-dev-ui-shared.mjs +1802 -0
- package/fesm2022/zambon-dev-ui-shared.mjs.map +1 -0
- package/fesm2022/zambon-dev-ui-testing.mjs +80 -0
- package/fesm2022/zambon-dev-ui-testing.mjs.map +1 -0
- package/fesm2022/zambon-dev-ui.mjs +8 -0
- package/fesm2022/zambon-dev-ui.mjs.map +1 -0
- package/i18n/framework/en.json +63 -0
- package/i18n/framework/pt.json +63 -0
- package/i18n/shared/external-content/en.json +12 -0
- package/i18n/shared/external-content/pt.json +12 -0
- package/i18n/shared/language-selector/en.json +5 -0
- package/i18n/shared/language-selector/pt.json +5 -0
- package/i18n/shared/login/en.json +14 -0
- package/i18n/shared/login/pt.json +14 -0
- package/i18n/shared/operations-history/en.json +5 -0
- package/i18n/shared/operations-history/pt.json +5 -0
- package/i18n/shared/services-history/en.json +14 -0
- package/i18n/shared/services-history/pt.json +14 -0
- package/i18n/shared/top-bar/en.json +7 -0
- package/i18n/shared/top-bar/pt.json +7 -0
- package/package.json +106 -4
- package/styles/common.scss +10 -0
- package/styles/components/button.scss +101 -0
- package/styles/components/card.scss +13 -0
- package/styles/components/dropdown-static.scss +35 -0
- package/styles/components/input-group.scss +44 -0
- package/styles/components/input.scss +99 -0
- package/styles/components/label.scss +3 -0
- package/styles/components/row.scss +48 -0
- package/styles/mixins/background.scss +31 -0
- package/styles/theme.css +75 -0
- package/styles/variables.scss +60 -0
- package/types/zambon-dev-ui-framework.d.ts +1066 -0
- package/types/zambon-dev-ui-library.d.ts +1233 -0
- package/types/zambon-dev-ui-shared.d.ts +655 -0
- package/types/zambon-dev-ui-testing.d.ts +56 -0
- package/types/zambon-dev-ui.d.ts +3 -0
|
@@ -0,0 +1,3997 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, ApplicationRef, Inject, signal, Injectable, viewChild, input, model, computed, effect, Component, output, NgModule, forwardRef, afterEveryRender, ViewChild, InjectionToken, Input, EventEmitter, Output } from '@angular/core';
|
|
3
|
+
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
|
|
4
|
+
import * as i1$3 from '@zambon-dev/ui/library';
|
|
5
|
+
import { RibbonGroupChild, RibbonButtonComponent, DataProviderService, FormService, ModalComponent, DataGridDataset, DisplayControls, RibbonComponent, GroupContainerComponent, RibbonGroupComponent, DataGridConfigsProvider, MultiSelectResultDataset, RouterFormatter, MultiEditorComponent, MultiEditorDataset, MultiSelectComponent } from '@zambon-dev/ui/library';
|
|
6
|
+
export { DateValidators } from '@zambon-dev/ui/library';
|
|
7
|
+
import { ReplaySubject, take, map, switchMap, of, Subject, startWith, takeUntil, filter, skip, tap, throwError } from 'rxjs';
|
|
8
|
+
import { __decorate } from 'tslib';
|
|
9
|
+
import * as i1 from '@angular/common';
|
|
10
|
+
import { NgClass, NgTemplateOutlet } from '@angular/common';
|
|
11
|
+
import * as i1$1 from '@angular/router';
|
|
12
|
+
import { Router, NavigationEnd, RouterModule, ActivatedRoute } from '@angular/router';
|
|
13
|
+
import { TranslatePipe, TranslateService, TranslateLoader, provideTranslateService } from '@ngx-translate/core';
|
|
14
|
+
import * as i1$2 from '@angular/forms';
|
|
15
|
+
import { ControlContainer, FormGroupDirective, FormBuilder } from '@angular/forms';
|
|
16
|
+
import { HttpBackend, HttpErrorResponse } from '@angular/common/http';
|
|
17
|
+
import { MultiTranslateHttpLoader } from 'ngx-translate-multi-http-loader';
|
|
18
|
+
|
|
19
|
+
const ADMINISTRATIVE_MASTER_ACTION = 'AdministrativeMaster';
|
|
20
|
+
class AuthService {
|
|
21
|
+
jwtHelper;
|
|
22
|
+
tabService;
|
|
23
|
+
//#region ViewChilds, Inputs, Outputs
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region Variables
|
|
26
|
+
actionsCache$ = new ReplaySubject(1);
|
|
27
|
+
isActionsCacheInitialized = false;
|
|
28
|
+
get isAuthenticated() {
|
|
29
|
+
return this.username !== null;
|
|
30
|
+
}
|
|
31
|
+
get isTokenExpired() {
|
|
32
|
+
return this.token !== null && this.jwtHelper.isTokenExpired(this.token);
|
|
33
|
+
}
|
|
34
|
+
get token() {
|
|
35
|
+
return localStorage.getItem('token') ?? sessionStorage.getItem('token');
|
|
36
|
+
}
|
|
37
|
+
get userID() {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
get refreshToken() {
|
|
41
|
+
return localStorage.getItem('refreshToken') ?? sessionStorage.getItem('refreshToken');
|
|
42
|
+
}
|
|
43
|
+
get userInfo() {
|
|
44
|
+
return localStorage.getItem('userInfo') ?? sessionStorage.getItem('userInfo');
|
|
45
|
+
}
|
|
46
|
+
get username() {
|
|
47
|
+
return localStorage.getItem('username') ?? sessionStorage.getItem('username');
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region Constructor and Angular life cycle methods
|
|
51
|
+
constructor(jwtHelper, tabService) {
|
|
52
|
+
this.jwtHelper = jwtHelper;
|
|
53
|
+
this.tabService = tabService;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region Event handlers
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region Public methods
|
|
59
|
+
checkActionIsAllowed(actionToCheck) {
|
|
60
|
+
if (!this.isActionsCacheInitialized) {
|
|
61
|
+
this.isActionsCacheInitialized = true;
|
|
62
|
+
this.getActions()
|
|
63
|
+
.pipe(take(1))
|
|
64
|
+
.subscribe((actions) => {
|
|
65
|
+
this.actionsCache$.next(actions);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return this.actionsCache$.pipe(map((actions) => actions.includes(this.adminAction) || actions.includes(actionToCheck)));
|
|
69
|
+
}
|
|
70
|
+
checkActionsAreAllowed(actionsToCheck, checkForAdministrativeMaster = true) {
|
|
71
|
+
if (!this.isActionsCacheInitialized) {
|
|
72
|
+
this.isActionsCacheInitialized = true;
|
|
73
|
+
this.getActions()
|
|
74
|
+
.pipe(take(1))
|
|
75
|
+
.subscribe((actions) => {
|
|
76
|
+
this.actionsCache$.next(actions);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return this.actionsCache$.pipe(switchMap((actions) => of(actionsToCheck.map((actionsToCheck) => (checkForAdministrativeMaster && actions.indexOf(ADMINISTRATIVE_MASTER_ACTION) >= 0) ||
|
|
80
|
+
actions.indexOf(actionsToCheck) >= 0))));
|
|
81
|
+
}
|
|
82
|
+
signOut() {
|
|
83
|
+
localStorage.clear();
|
|
84
|
+
sessionStorage.clear();
|
|
85
|
+
this.tabService.closeAllTabs();
|
|
86
|
+
this.resetActionsCache();
|
|
87
|
+
}
|
|
88
|
+
setStorage(key, value, useLocalStorage = null) {
|
|
89
|
+
if (useLocalStorage === null) {
|
|
90
|
+
useLocalStorage = sessionStorage.getItem(key) === null;
|
|
91
|
+
}
|
|
92
|
+
if (value) {
|
|
93
|
+
if (useLocalStorage) {
|
|
94
|
+
localStorage.setItem(key, value);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
sessionStorage.setItem(key, value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
localStorage.removeItem(key);
|
|
102
|
+
sessionStorage.removeItem(key);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region Private methods
|
|
107
|
+
resetActionsCache() {
|
|
108
|
+
this.isActionsCacheInitialized = false;
|
|
109
|
+
this.actionsCache$ = new ReplaySubject(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let CustomReuseStrategy = class CustomReuseStrategy {
|
|
114
|
+
//#region ViewChilds, Inputs, Outputs
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region Variables
|
|
117
|
+
clones = {};
|
|
118
|
+
redirects = {};
|
|
119
|
+
tabService;
|
|
120
|
+
applicationRef = inject(ApplicationRef);
|
|
121
|
+
cachedHandles = {};
|
|
122
|
+
componentIDs = new WeakMap();
|
|
123
|
+
nextComponentID = 1;
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region Properties
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region Constructor and Angular life cycle methods
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region Event handlers
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region Public methods
|
|
132
|
+
clearAllHandles() {
|
|
133
|
+
this.cachedHandles = {};
|
|
134
|
+
}
|
|
135
|
+
clearHandle(url) {
|
|
136
|
+
const keysToRemove = Object.keys(this.cachedHandles).filter((key) => key.startsWith(`${url}-`));
|
|
137
|
+
keysToRemove.forEach((key) => {
|
|
138
|
+
const handle = this.cachedHandles[key];
|
|
139
|
+
if (handle) {
|
|
140
|
+
if (handle?.componentRef?.instance?.ngOnDestroy) {
|
|
141
|
+
handle.componentRef.instance.ngOnDestroy();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
delete this.cachedHandles[key];
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
retrieve(route) {
|
|
148
|
+
if (!route.component) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const cacheKey = this.getCacheKey(route);
|
|
152
|
+
const handle = this.cachedHandles[cacheKey];
|
|
153
|
+
if (handle) {
|
|
154
|
+
// Schedule global change detection after Angular finishes reattaching the component.
|
|
155
|
+
// Without this, reattached components may render stale views (e.g., empty ribbon)
|
|
156
|
+
// because NgTemplateOutlet does not re-create its embedded view automatically.
|
|
157
|
+
// ApplicationRef.tick() ensures the entire app tree is checked, including embedded views.
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
this.applicationRef.tick();
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return handle;
|
|
163
|
+
}
|
|
164
|
+
shouldAttach(route) {
|
|
165
|
+
const url = this.getUrlFromRoute(route);
|
|
166
|
+
const hasComponent = !!route.component;
|
|
167
|
+
if ((this.tabService?.isUrlOpen(url) ?? false) && hasComponent) {
|
|
168
|
+
const cacheKey = this.getCacheKey(route);
|
|
169
|
+
const isCached = !!this.cachedHandles[cacheKey];
|
|
170
|
+
return isCached;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
shouldDetach(route) {
|
|
175
|
+
const url = this.getUrlFromRoute(route);
|
|
176
|
+
const hasComponent = !!route.component;
|
|
177
|
+
return (this.tabService?.isUrlOpen(url) ?? false) && hasComponent;
|
|
178
|
+
}
|
|
179
|
+
shouldReuseRoute(future, current) {
|
|
180
|
+
const futureUrl = this.getUrlFromRoute(future);
|
|
181
|
+
const currentUrl = this.getUrlFromRoute(current);
|
|
182
|
+
return ((futureUrl === currentUrl && future.routeConfig === current.routeConfig) ||
|
|
183
|
+
this.redirects[currentUrl] === futureUrl ||
|
|
184
|
+
this.clones[futureUrl] === currentUrl);
|
|
185
|
+
}
|
|
186
|
+
store(route, handle) {
|
|
187
|
+
const cacheKey = this.getCacheKey(route);
|
|
188
|
+
this.cachedHandles[cacheKey] = handle;
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region Private methods
|
|
192
|
+
getCacheKey(route) {
|
|
193
|
+
let url = this.getUrlFromRoute(route);
|
|
194
|
+
if (this.clones[url]) {
|
|
195
|
+
url = this.clones[url];
|
|
196
|
+
}
|
|
197
|
+
// The component is identified by identity, never by name. A production build renames every
|
|
198
|
+
// class: esbuild wraps each component as `X = (() => { class i { } return i; })()`, so
|
|
199
|
+
// `component.name` is a single mangled letter that the whole chunk shares. Nested empty-path
|
|
200
|
+
// routes already resolve to the same URL, because an empty path contributes no segment, so a
|
|
201
|
+
// key built from the name collapsed a screen's tab view and its list into one entry: storing
|
|
202
|
+
// one overwrote the other, both levels were then handed the same detached view, and Angular
|
|
203
|
+
// blew the stack building a router state whose node was its own descendant. That reproduced
|
|
204
|
+
// only in a minified build, and only on the screens whose chunk happened to mangle to the
|
|
205
|
+
// same letter as the framework's.
|
|
206
|
+
//
|
|
207
|
+
// The depth is part of the key too, so two sibling routes sharing a component type under one
|
|
208
|
+
// URL cannot collide either.
|
|
209
|
+
return `${url}-${this.getRouteDepth(route)}-${this.getComponentID(route.component)}`;
|
|
210
|
+
}
|
|
211
|
+
getComponentID(component) {
|
|
212
|
+
if (typeof component !== 'function') {
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
let componentID = this.componentIDs.get(component);
|
|
216
|
+
if (componentID === undefined) {
|
|
217
|
+
componentID = this.nextComponentID++;
|
|
218
|
+
this.componentIDs.set(component, componentID);
|
|
219
|
+
}
|
|
220
|
+
return componentID;
|
|
221
|
+
}
|
|
222
|
+
getRouteDepth(route) {
|
|
223
|
+
let depth = 0;
|
|
224
|
+
let current = route.parent;
|
|
225
|
+
while (current) {
|
|
226
|
+
depth++;
|
|
227
|
+
current = current.parent;
|
|
228
|
+
}
|
|
229
|
+
return depth;
|
|
230
|
+
}
|
|
231
|
+
getUrlFromRoute(route) {
|
|
232
|
+
const segments = [];
|
|
233
|
+
const buildRoute = (route) => {
|
|
234
|
+
if (route) {
|
|
235
|
+
if (route.url.length) {
|
|
236
|
+
segments.push(...route.url.map((segment) => segment.path));
|
|
237
|
+
}
|
|
238
|
+
buildRoute(route.parent);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
buildRoute(route);
|
|
242
|
+
return `/${segments.reverse().join('/')}`;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
CustomReuseStrategy = __decorate([
|
|
246
|
+
Inject({})
|
|
247
|
+
], CustomReuseStrategy);
|
|
248
|
+
|
|
249
|
+
class TabViewService {
|
|
250
|
+
//#region ViewChilds, Inputs, Outputs
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region Variables
|
|
253
|
+
onUpdateRibbonTemplate = new ReplaySubject(1);
|
|
254
|
+
onViewChanged = new Subject();
|
|
255
|
+
_activeView = signal(undefined, /* @ts-ignore */
|
|
256
|
+
...(ngDevMode ? [{ debugName: "_activeView" }] : /* istanbul ignore next */ []));
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region Properties
|
|
259
|
+
get activeView() {
|
|
260
|
+
return this._activeView() ?? '';
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region Constructor and Angular life cycle methods
|
|
264
|
+
//#endregion
|
|
265
|
+
//#region Event handlers
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region Public methods
|
|
268
|
+
setActiveView(viewId) {
|
|
269
|
+
if (viewId === this._activeView()) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
this._activeView.set(viewId);
|
|
273
|
+
this.onViewChanged.next(viewId);
|
|
274
|
+
}
|
|
275
|
+
updateRibbonTemplate(template) {
|
|
276
|
+
this.onUpdateRibbonTemplate.next(template);
|
|
277
|
+
}
|
|
278
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
279
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewService });
|
|
280
|
+
}
|
|
281
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewService, decorators: [{
|
|
282
|
+
type: Injectable
|
|
283
|
+
}] });
|
|
284
|
+
|
|
285
|
+
class TabService {
|
|
286
|
+
applicationRef;
|
|
287
|
+
location;
|
|
288
|
+
router;
|
|
289
|
+
//#region ViewChilds, Inputs, Outputs
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region Variables
|
|
292
|
+
// Tab state is a tree of arrays mutated in place -- spliced, pushed, and with ITab objects
|
|
293
|
+
// whose titles are rewritten as they resolve. A signal over the array could not see any of
|
|
294
|
+
// that, so this counter stands in for it: every write below bumps it, and the public getters
|
|
295
|
+
// read it, which is what makes a template that reads them re-render. It is a bridge, not the
|
|
296
|
+
// destination -- the tab state itself still wants to become signals. See
|
|
297
|
+
// docs/guides/reactivity.md.
|
|
298
|
+
revision = signal(0, /* @ts-ignore */
|
|
299
|
+
...(ngDevMode ? [{ debugName: "revision" }] : /* istanbul ignore next */ []));
|
|
300
|
+
activeTabIndex = -1;
|
|
301
|
+
customReuseStrategy;
|
|
302
|
+
displayTitleIndices = [];
|
|
303
|
+
openTabs = [];
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region Properties
|
|
306
|
+
get activeTabHistory() {
|
|
307
|
+
this.revision();
|
|
308
|
+
return this.openTabs[this.activeTabIndex] ?? [];
|
|
309
|
+
}
|
|
310
|
+
get activeTabs() {
|
|
311
|
+
this.revision();
|
|
312
|
+
return this.openTabs.map((tabs) => tabs[0]) ?? [];
|
|
313
|
+
}
|
|
314
|
+
get activeTabsDisplayTitles() {
|
|
315
|
+
this.revision();
|
|
316
|
+
return this.openTabs.map((tabs, i) => {
|
|
317
|
+
const displayIndex = this.displayTitleIndices[i] ?? 0;
|
|
318
|
+
return tabs[displayIndex]?.title;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
get activeTabsLoadingStates() {
|
|
322
|
+
this.revision();
|
|
323
|
+
return this.openTabs.map((tabs, i) => {
|
|
324
|
+
const displayIndex = this.displayTitleIndices[i] ?? 0;
|
|
325
|
+
return tabs[displayIndex]?.isTitleLoading ?? true;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
get activeTab() {
|
|
329
|
+
if (this.activeTabIndex < 0) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
return this.openTabs[this.activeTabIndex];
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region Constructor and Angular life cycle methods
|
|
336
|
+
constructor(applicationRef, location, router, routeReuseStrategy) {
|
|
337
|
+
this.applicationRef = applicationRef;
|
|
338
|
+
this.location = location;
|
|
339
|
+
this.router = router;
|
|
340
|
+
this.customReuseStrategy = routeReuseStrategy;
|
|
341
|
+
this.customReuseStrategy.tabService = this;
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region Event handlers
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region Public methods
|
|
347
|
+
activateTab(tab) {
|
|
348
|
+
this.activeTabIndex = this.activeTabs.indexOf(tab);
|
|
349
|
+
this.touched();
|
|
350
|
+
this.navigateTo(this.currentViewOf(this.activeTabIndex));
|
|
351
|
+
}
|
|
352
|
+
cloneCurrentTab(url) {
|
|
353
|
+
if (!this.activeTab) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const tab = this.activeTab[this.activeTab.length - 1];
|
|
357
|
+
this.customReuseStrategy.clones[url] = tab.url;
|
|
358
|
+
if (tab.clones.indexOf(url) === -1) {
|
|
359
|
+
tab.clones.push(url);
|
|
360
|
+
}
|
|
361
|
+
this.navigateTo(tab, url);
|
|
362
|
+
}
|
|
363
|
+
closeActiveTab() {
|
|
364
|
+
this.closeTab(this.activeTabIndex);
|
|
365
|
+
}
|
|
366
|
+
closeAllTabs() {
|
|
367
|
+
this.openTabs = [];
|
|
368
|
+
this.touched();
|
|
369
|
+
this.displayTitleIndices = [];
|
|
370
|
+
this.touched();
|
|
371
|
+
this.activeTabIndex = -1;
|
|
372
|
+
this.touched();
|
|
373
|
+
this.customReuseStrategy.clearAllHandles();
|
|
374
|
+
}
|
|
375
|
+
closeTab(index) {
|
|
376
|
+
if (index > this.activeTabs.length - 1 || this.activeTabs.length === 0) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const wasActive = index === this.activeTabIndex;
|
|
380
|
+
const closedTab = this.openTabs[index];
|
|
381
|
+
this.openTabs.splice(index, 1);
|
|
382
|
+
this.touched();
|
|
383
|
+
this.displayTitleIndices.splice(index, 1);
|
|
384
|
+
this.touched();
|
|
385
|
+
if (wasActive) {
|
|
386
|
+
// Closed the active tab — pick an adjacent tab to focus, and put it back on the view it was
|
|
387
|
+
// left on. This used to navigate to activeTabs[i], which is that tab's *root*: a tab opened
|
|
388
|
+
// as a list and then drilled into a record went back to the list, under the record's own
|
|
389
|
+
// tab, until the tab was clicked again.
|
|
390
|
+
if (this.activeTabIndex >= this.openTabs.length) {
|
|
391
|
+
this.activeTabIndex = this.openTabs.length - 1;
|
|
392
|
+
this.touched();
|
|
393
|
+
}
|
|
394
|
+
// Undefined once the last tab is gone, which navigateTo answers with the home URL.
|
|
395
|
+
this.navigateTo(this.currentViewOf(this.activeTabIndex));
|
|
396
|
+
}
|
|
397
|
+
else if (index < this.activeTabIndex) {
|
|
398
|
+
// Closed a tab before the active one — shift index to keep the same tab active.
|
|
399
|
+
this.activeTabIndex--;
|
|
400
|
+
}
|
|
401
|
+
closedTab.forEach((tab) => {
|
|
402
|
+
if (!this.isUrlOpen(tab.url)) {
|
|
403
|
+
// Tab is not in use by any other tab, we're clear to remove.
|
|
404
|
+
this.customReuseStrategy.clearHandle(tab.url);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
isTabActive(tab) {
|
|
409
|
+
return this.activeTabs[this.activeTabIndex] === tab;
|
|
410
|
+
}
|
|
411
|
+
isUrlActive(url, checkForClones = false) {
|
|
412
|
+
if (!this.activeTab) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
const tab = this.activeTab[this.activeTab.length - 1];
|
|
416
|
+
if (tab.url === url) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
else if (checkForClones) {
|
|
420
|
+
return tab.clones.indexOf(url) !== -1;
|
|
421
|
+
}
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
isUrlOpen(url) {
|
|
425
|
+
const tabs = this.openTabs.flatMap((tabs) => tabs);
|
|
426
|
+
return this.isTabExists(tabs, url);
|
|
427
|
+
}
|
|
428
|
+
navigateBackOrCloseActiveTab() {
|
|
429
|
+
if (!this.activeTab || this.activeTab.length === 0) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (this.activeTab.length <= 1) {
|
|
433
|
+
this.closeTab(this.activeTabIndex);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
const previousEntry = this.activeTab[this.activeTab.length - 2];
|
|
437
|
+
this.navigateCurrentTabBack(previousEntry);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
navigateCurrentTab(tab) {
|
|
441
|
+
if (!this.activeTab) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
// Check other tabs for entity match first, then fall back to current behavior.
|
|
445
|
+
if (tab.entityBaseUrl) {
|
|
446
|
+
const entityMatchIndex = this.findTabIndexByEntityMatch(tab.entityBaseUrl, this.activeTabIndex);
|
|
447
|
+
if (entityMatchIndex !== -1) {
|
|
448
|
+
this.activeTabIndex = entityMatchIndex;
|
|
449
|
+
this.touched();
|
|
450
|
+
const otherTab = this.openTabs[entityMatchIndex];
|
|
451
|
+
this.navigateTo(otherTab[otherTab.length - 1]);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
// Fallback for tabs without entityBaseUrl (existing behavior).
|
|
457
|
+
const showingIndex = this.findTabIndexByCurrentView(tab.url, this.activeTabIndex, true);
|
|
458
|
+
if (showingIndex !== -1) {
|
|
459
|
+
this.activeTabIndex = showingIndex;
|
|
460
|
+
this.touched();
|
|
461
|
+
const otherTab = this.openTabs[showingIndex];
|
|
462
|
+
this.navigateTo(otherTab[otherTab.length - 1]);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// Check if URL already in current tab's stack — navigate back.
|
|
467
|
+
const tabIsAlreadyOpen = this.findTab(this.activeTab, tab.url);
|
|
468
|
+
if (tabIsAlreadyOpen) {
|
|
469
|
+
this.navigateCurrentTabBack(tabIsAlreadyOpen);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
// Inherit title from an existing tab entry with the same URL, if available.
|
|
473
|
+
this.inheritTitleIfKnown(tab);
|
|
474
|
+
// Push new entry onto the stack.
|
|
475
|
+
let clonedUrl = undefined;
|
|
476
|
+
if (tab.clones.length > 0) {
|
|
477
|
+
clonedUrl = tab.clones[0];
|
|
478
|
+
this.customReuseStrategy.clones[clonedUrl] = tab.url;
|
|
479
|
+
}
|
|
480
|
+
this.activeTab.push(tab);
|
|
481
|
+
this.displayTitleIndices[this.activeTabIndex] = this.activeTab.length - 1;
|
|
482
|
+
this.touched();
|
|
483
|
+
this.navigateTo(tab, clonedUrl);
|
|
484
|
+
}
|
|
485
|
+
navigateCurrentTabBack(tab) {
|
|
486
|
+
if (!this.activeTab) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
// If another tab is currently showing this URL, focus it instead.
|
|
490
|
+
const otherTabIndex = this.findTabIndexByCurrentView(tab.url, this.activeTabIndex);
|
|
491
|
+
if (otherTabIndex !== -1) {
|
|
492
|
+
this.activeTabIndex = otherTabIndex;
|
|
493
|
+
this.touched();
|
|
494
|
+
const otherTab = this.openTabs[otherTabIndex];
|
|
495
|
+
this.navigateTo(otherTab[otherTab.length - 1]);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const lastIndex = this.activeTab.lastIndexOf(tab);
|
|
499
|
+
if (lastIndex !== -1) {
|
|
500
|
+
const removedEntries = this.activeTab.slice(lastIndex + 1);
|
|
501
|
+
this.openTabs[this.activeTabIndex] = this.activeTab.slice(0, lastIndex + 1);
|
|
502
|
+
this.touched();
|
|
503
|
+
if (this.displayTitleIndices[this.activeTabIndex] > lastIndex) {
|
|
504
|
+
this.displayTitleIndices[this.activeTabIndex] = lastIndex;
|
|
505
|
+
this.touched();
|
|
506
|
+
}
|
|
507
|
+
// Every open tab keeps its views in the reuse cache, the one it shows or one in its history,
|
|
508
|
+
// so a view leaves the cache only once no tab has its URL open. This cleared every removed
|
|
509
|
+
// URL unconditionally and could take a view out from under another tab; closeTab and
|
|
510
|
+
// replaceCurrentTabSubView already made this check. The history is cut first, so the removed
|
|
511
|
+
// entries no longer count as open themselves.
|
|
512
|
+
removedEntries.forEach((entry) => {
|
|
513
|
+
if (!this.isUrlOpen(entry.url)) {
|
|
514
|
+
this.customReuseStrategy.clearHandle(entry.url);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
this.navigateTo(tab);
|
|
519
|
+
}
|
|
520
|
+
navigateTo(tab, clonedUrl) {
|
|
521
|
+
let url = tab?.url ?? '/';
|
|
522
|
+
if (clonedUrl) {
|
|
523
|
+
url = clonedUrl;
|
|
524
|
+
}
|
|
525
|
+
this.router.navigate([url], { queryParams: tab?.queryParams }).then((navigated) => {
|
|
526
|
+
this.customReuseStrategy.redirects = {};
|
|
527
|
+
// Clean up clone mapping after successful clone-based navigation.
|
|
528
|
+
if (navigated && this.customReuseStrategy.clones[url]) {
|
|
529
|
+
delete this.customReuseStrategy.clones[url];
|
|
530
|
+
}
|
|
531
|
+
this.applicationRef.tick();
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
openTab(tab) {
|
|
535
|
+
// 1. Exact URL match at top-of-stack — focus that tab.
|
|
536
|
+
const exactMatchIndex = this.findTabIndexByCurrentView(tab.url);
|
|
537
|
+
if (exactMatchIndex !== -1) {
|
|
538
|
+
this.activeTabIndex = exactMatchIndex;
|
|
539
|
+
this.touched();
|
|
540
|
+
const currentTab = this.openTabs[exactMatchIndex];
|
|
541
|
+
this.navigateTo(currentTab[currentTab.length - 1]);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
// 2. Entity match (only when entityBaseUrl is set) — focus that tab, keep current view.
|
|
545
|
+
if (tab.entityBaseUrl) {
|
|
546
|
+
const entityMatchIndex = this.findTabIndexByEntityMatch(tab.entityBaseUrl);
|
|
547
|
+
if (entityMatchIndex !== -1) {
|
|
548
|
+
this.activeTabIndex = entityMatchIndex;
|
|
549
|
+
this.touched();
|
|
550
|
+
const existingTab = this.openTabs[entityMatchIndex];
|
|
551
|
+
this.navigateTo(existingTab[existingTab.length - 1]);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// 3. No match — open a new tab.
|
|
556
|
+
this.inheritTitleIfKnown(tab);
|
|
557
|
+
this.openTabs.push([tab]);
|
|
558
|
+
this.touched();
|
|
559
|
+
this.displayTitleIndices.push(0);
|
|
560
|
+
this.touched();
|
|
561
|
+
this.activeTabIndex = this.openTabs.length - 1;
|
|
562
|
+
this.touched();
|
|
563
|
+
this.navigateTo(tab);
|
|
564
|
+
}
|
|
565
|
+
replaceCurrentTabSubView(baseUrl, tab) {
|
|
566
|
+
if (!this.activeTab || this.activeTab.length === 0) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// Find the anchor entry (the detail view whose URL matches baseUrl).
|
|
570
|
+
// Keep all entries up to and including the anchor.
|
|
571
|
+
const anchorIndex = this.activeTab.findIndex((t) => t.url === baseUrl);
|
|
572
|
+
const keepUntil = anchorIndex !== -1 ? anchorIndex + 1 : 1;
|
|
573
|
+
const keptEntries = this.activeTab.slice(0, keepUntil);
|
|
574
|
+
const removedEntries = this.activeTab.slice(keepUntil);
|
|
575
|
+
// Update history: keep entries up to anchor, optionally add new sub-view
|
|
576
|
+
if (tab) {
|
|
577
|
+
// Propagate entityBaseUrl from the anchor so entity matching works on sub-views.
|
|
578
|
+
const anchor = keptEntries[anchorIndex !== -1 ? anchorIndex : 0];
|
|
579
|
+
if (!tab.entityBaseUrl && anchor?.entityBaseUrl) {
|
|
580
|
+
tab.entityBaseUrl = anchor.entityBaseUrl;
|
|
581
|
+
}
|
|
582
|
+
this.openTabs[this.activeTabIndex] = [...keptEntries, tab];
|
|
583
|
+
this.touched();
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
this.openTabs[this.activeTabIndex] = keptEntries;
|
|
587
|
+
this.touched();
|
|
588
|
+
}
|
|
589
|
+
// Clear reuse-strategy handles for removed entries no longer open anywhere
|
|
590
|
+
removedEntries.forEach((entry) => {
|
|
591
|
+
if (!this.isUrlOpen(entry.url)) {
|
|
592
|
+
this.customReuseStrategy.clearHandle(entry.url);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
// Navigate to the topmost entry
|
|
596
|
+
const current = this.openTabs[this.activeTabIndex];
|
|
597
|
+
this.navigateTo(current[current.length - 1]);
|
|
598
|
+
}
|
|
599
|
+
redirectCurrentTab(url, updateLocation = true) {
|
|
600
|
+
if (!this.activeTab) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const tab = this.activeTab[this.activeTab.length - 1];
|
|
604
|
+
// Update entityBaseUrl if it was pointing to the old URL (e.g., /new → /:id after save).
|
|
605
|
+
if (tab.entityBaseUrl === tab.url) {
|
|
606
|
+
tab.entityBaseUrl = url;
|
|
607
|
+
}
|
|
608
|
+
tab.url = url;
|
|
609
|
+
tab.queryParams = {};
|
|
610
|
+
if (updateLocation) {
|
|
611
|
+
this.location.replaceState(url);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
setRouteRedirect(fromUrl, toUrl) {
|
|
615
|
+
this.customReuseStrategy.redirects[fromUrl] = toUrl;
|
|
616
|
+
}
|
|
617
|
+
updateActiveTabRootTitle(title) {
|
|
618
|
+
if (this.activeTab && this.activeTab.length > 0) {
|
|
619
|
+
this.activeTab[0].title = title;
|
|
620
|
+
this.touched();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
updateTabTitle(url, title) {
|
|
624
|
+
this.openTabs
|
|
625
|
+
.flatMap((tabs) => tabs)
|
|
626
|
+
.filter((tab) => tab.url === url)
|
|
627
|
+
.forEach((tab) => {
|
|
628
|
+
tab.title = title;
|
|
629
|
+
this.touched();
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region Private methods
|
|
634
|
+
/** Announces that the tab state changed, so views reading it re-render. */
|
|
635
|
+
touched() {
|
|
636
|
+
this.revision.update((revision) => revision + 1);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* The view a tab is showing: the top of its history stack.
|
|
640
|
+
*
|
|
641
|
+
* Not `activeTabs[index]`, which is the stack's *root* -- the entry the strip takes each tab's
|
|
642
|
+
* label from. The two are the same only for a tab nobody has drilled into, which is why
|
|
643
|
+
* reaching for the wrong one survives most testing. Undefined for an index with no tab, which
|
|
644
|
+
* is what closing the last tab leaves behind.
|
|
645
|
+
*/
|
|
646
|
+
currentViewOf(index) {
|
|
647
|
+
const history = this.openTabs[index];
|
|
648
|
+
return history?.[history.length - 1];
|
|
649
|
+
}
|
|
650
|
+
findTab(tabs, url) {
|
|
651
|
+
return tabs.find((tab) => this.matchTabUrl(tab, url));
|
|
652
|
+
}
|
|
653
|
+
findTabIndex(tabs, url) {
|
|
654
|
+
return tabs.findIndex((tab) => this.matchTabUrl(tab, url));
|
|
655
|
+
}
|
|
656
|
+
isTabExists(tabs, url) {
|
|
657
|
+
return tabs.some((tab) => this.matchTabUrl(tab, url));
|
|
658
|
+
}
|
|
659
|
+
findTabIndexByEntityMatch(entityBaseUrl, excludeIndex = -1) {
|
|
660
|
+
return this.openTabs.findIndex((tabStack, index) => {
|
|
661
|
+
if (index === excludeIndex) {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
// Only check the current view (top of stack), not the full history.
|
|
665
|
+
const currentView = tabStack[tabStack.length - 1];
|
|
666
|
+
return currentView.entityBaseUrl === entityBaseUrl || currentView.url === entityBaseUrl;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
findTabIndexByCurrentView(url, excludeIndex = -1, includeChildRoutes = false) {
|
|
670
|
+
return this.openTabs.findIndex((tabStack, index) => {
|
|
671
|
+
if (index === excludeIndex) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
const currentView = tabStack[tabStack.length - 1];
|
|
675
|
+
return this.matchTabUrl(currentView, url) || (includeChildRoutes && this.isChildRoute(currentView.url, url));
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
inheritTitleIfKnown(tab) {
|
|
679
|
+
if (tab.title || !tab.isTitleLoading) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const existing = this.openTabs
|
|
683
|
+
.flatMap((stack) => stack)
|
|
684
|
+
.find((entry) => entry.url === tab.url && !entry.isTitleLoading && !!entry.title);
|
|
685
|
+
if (existing) {
|
|
686
|
+
tab.title = existing.title;
|
|
687
|
+
this.touched();
|
|
688
|
+
tab.isTitleLoading = false;
|
|
689
|
+
this.touched();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
isChildRoute(currentUrl, parentUrl) {
|
|
693
|
+
return currentUrl.startsWith(parentUrl + '/');
|
|
694
|
+
}
|
|
695
|
+
matchTabUrl(tab, url) {
|
|
696
|
+
return tab.url === url || tab.clones.indexOf(url) !== -1;
|
|
697
|
+
}
|
|
698
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabService, deps: [{ token: i0.ApplicationRef }, { token: i1.Location }, { token: i1$1.Router }, { token: i1$1.RouteReuseStrategy }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
699
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabService, providedIn: 'root' });
|
|
700
|
+
}
|
|
701
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabService, decorators: [{
|
|
702
|
+
type: Injectable,
|
|
703
|
+
args: [{
|
|
704
|
+
providedIn: 'root',
|
|
705
|
+
}]
|
|
706
|
+
}], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i1.Location }, { type: i1$1.Router }, { type: i1$1.RouteReuseStrategy }] });
|
|
707
|
+
|
|
708
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
709
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
710
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
711
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
712
|
+
class BaseButton extends RibbonGroupChild {
|
|
713
|
+
//#region ViewChilds, Inputs, Outputs
|
|
714
|
+
button = viewChild(RibbonButtonComponent, /* @ts-ignore */
|
|
715
|
+
...(ngDevMode ? [{ debugName: "button" }] : /* istanbul ignore next */ []));
|
|
716
|
+
allowedActions = input([], /* @ts-ignore */
|
|
717
|
+
...(ngDevMode ? [{ debugName: "allowedActions" }] : /* istanbul ignore next */ []));
|
|
718
|
+
// Models rather than inputs: a subclass writes all three from a constructor, a lifecycle hook
|
|
719
|
+
// or a subscription -- the filters button disables itself until its grid has loaded, the
|
|
720
|
+
// refresh button drives its own spinner, and the export, save and views buttons build their own
|
|
721
|
+
// option lists. A plain field told Angular nothing.
|
|
722
|
+
disabled = model(false, /* @ts-ignore */
|
|
723
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
724
|
+
loading = model(false, /* @ts-ignore */
|
|
725
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
726
|
+
options = model([], /* @ts-ignore */
|
|
727
|
+
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
728
|
+
iconSize = input('large', /* @ts-ignore */
|
|
729
|
+
...(ngDevMode ? [{ debugName: "iconSize" }] : /* istanbul ignore next */ []));
|
|
730
|
+
//#endregion
|
|
731
|
+
//#region Variables
|
|
732
|
+
authService = inject(AuthService);
|
|
733
|
+
destroy$ = new Subject();
|
|
734
|
+
/**
|
|
735
|
+
* Whether a button is allowed is one question asked of one input, so it is one stream rather
|
|
736
|
+
* than two fields written from a subscription. It also runs once: the old setter fired the
|
|
737
|
+
* check and the constructor fired it again, before any input had arrived.
|
|
738
|
+
*
|
|
739
|
+
* While the answer is outstanding the button is visible but not yet loaded, which is what
|
|
740
|
+
* `RibbonButtonComponent` already does with an option whose access has not come back.
|
|
741
|
+
*/
|
|
742
|
+
access = toSignal(toObservable(this.allowedActions).pipe(switchMap((allowedActions) => this.checkAccessIsAllowed(allowedActions))), { initialValue: { isLoaded: true, isVisible: true } });
|
|
743
|
+
/**
|
|
744
|
+
* The options whose access check has already been sent.
|
|
745
|
+
*
|
|
746
|
+
* Resolving one replaces that option with a copy carrying the answer, so the list below sees a
|
|
747
|
+
* new array and the check runs again over it. The copy has an answer and is skipped; this set
|
|
748
|
+
* is what stops an option still in flight from being asked a second time.
|
|
749
|
+
*/
|
|
750
|
+
requestedOptions = new WeakSet();
|
|
751
|
+
//#endregion
|
|
752
|
+
//#region Properties
|
|
753
|
+
isAccessLoaded = computed(() => this.access().isLoaded, /* @ts-ignore */
|
|
754
|
+
...(ngDevMode ? [{ debugName: "isAccessLoaded" }] : /* istanbul ignore next */ []));
|
|
755
|
+
isVisible = computed(() => this.access().isVisible, /* @ts-ignore */
|
|
756
|
+
...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
|
|
757
|
+
get isButtonDisabled() {
|
|
758
|
+
return this.disabled() || !this.isAccessLoaded();
|
|
759
|
+
}
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region Constructor and Angular life cycle methods
|
|
762
|
+
/**
|
|
763
|
+
* Asks for the access behind every option that declares one and has not been asked yet.
|
|
764
|
+
*
|
|
765
|
+
* An effect rather than a setter: `options` is written by this component's own subclasses as
|
|
766
|
+
* well as bound by a host application, and both have to reach the check.
|
|
767
|
+
*/
|
|
768
|
+
optionsAccess = effect(() => {
|
|
769
|
+
const pendingOptions = this.options().filter((option) => (option.allowedActions?.length ?? 0) > 0 &&
|
|
770
|
+
option.isAccessAllowed === undefined &&
|
|
771
|
+
!this.requestedOptions.has(option));
|
|
772
|
+
for (const option of pendingOptions) {
|
|
773
|
+
this.requestedOptions.add(option);
|
|
774
|
+
this.authService
|
|
775
|
+
.checkActionsAreAllowed(option.allowedActions)
|
|
776
|
+
.pipe(take(1))
|
|
777
|
+
.subscribe((result) => {
|
|
778
|
+
this.setOptionAccess(option, result.some((isAllowed) => isAllowed === true));
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}, /* @ts-ignore */
|
|
782
|
+
...(ngDevMode ? [{ debugName: "optionsAccess" }] : /* istanbul ignore next */ []));
|
|
783
|
+
ngOnDestroy() {
|
|
784
|
+
this.destroy$.next(true);
|
|
785
|
+
this.destroy$.complete();
|
|
786
|
+
}
|
|
787
|
+
//#endregion
|
|
788
|
+
//#region Event handlers
|
|
789
|
+
//#endregion
|
|
790
|
+
//#region Public methods
|
|
791
|
+
startLoading() {
|
|
792
|
+
this.button()?.startLoading();
|
|
793
|
+
}
|
|
794
|
+
finishLoading(status) {
|
|
795
|
+
this.button()?.finishLoading(status);
|
|
796
|
+
}
|
|
797
|
+
//#endregion
|
|
798
|
+
//#region Private methods
|
|
799
|
+
checkAccessIsAllowed(allowedActions) {
|
|
800
|
+
if (allowedActions.length === 0) {
|
|
801
|
+
return of({ isLoaded: true, isVisible: true });
|
|
802
|
+
}
|
|
803
|
+
return this.authService.checkActionsAreAllowed(allowedActions).pipe(take(1), map((result) => ({
|
|
804
|
+
isLoaded: true,
|
|
805
|
+
isVisible: result.some((isAllowed) => isAllowed === true),
|
|
806
|
+
})), startWith({ isLoaded: false, isVisible: true }));
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Replaces one option with a copy carrying its answer, leaving the others as they are.
|
|
810
|
+
*
|
|
811
|
+
* The list has to be a new array for the ribbon button to see it at all, and the untouched
|
|
812
|
+
* options have to keep their identity so the dropdown does not rebuild every row.
|
|
813
|
+
*/
|
|
814
|
+
setOptionAccess(option, isAccessAllowed) {
|
|
815
|
+
this.options.update((options) => options.map((candidate) => candidate === option ? { ...candidate, isAccessAllowed } : candidate));
|
|
816
|
+
}
|
|
817
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: BaseButton, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
818
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.7", type: BaseButton, isStandalone: true, selector: "ng-component", inputs: { allowedActions: { classPropertyName: "allowedActions", publicName: "allowedActions", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, iconSize: { classPropertyName: "iconSize", publicName: "iconSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", loading: "loadingChange", options: "optionsChange" }, viewQueries: [{ propertyName: "button", first: true, predicate: RibbonButtonComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
819
|
+
}
|
|
820
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: BaseButton, decorators: [{
|
|
821
|
+
type: Component,
|
|
822
|
+
args: [{ template: '' }]
|
|
823
|
+
}], propDecorators: { button: [{ type: i0.ViewChild, args: [i0.forwardRef(() => RibbonButtonComponent), { isSignal: true }] }], allowedActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedActions", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }, { type: i0.Output, args: ["loadingChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }, { type: i0.Output, args: ["optionsChange"] }], iconSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconSize", required: false }] }] } });
|
|
824
|
+
|
|
825
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
826
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
827
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
828
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
829
|
+
class BaseFormViewButton extends BaseButton {
|
|
830
|
+
//#region ViewChilds, Inputs, Outputs
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region Variables
|
|
833
|
+
dataProviderService;
|
|
834
|
+
formService;
|
|
835
|
+
//#endregion
|
|
836
|
+
//#region Properties
|
|
837
|
+
get isButtonDisabled() {
|
|
838
|
+
return super.isButtonDisabled || this.formService.loading;
|
|
839
|
+
}
|
|
840
|
+
//#endregion
|
|
841
|
+
//#region Constructor and Angular life cycle methods
|
|
842
|
+
constructor() {
|
|
843
|
+
super();
|
|
844
|
+
this.dataProviderService = inject((DataProviderService));
|
|
845
|
+
this.formService = inject(FormService);
|
|
846
|
+
}
|
|
847
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: BaseFormViewButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
848
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: BaseFormViewButton, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
849
|
+
}
|
|
850
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: BaseFormViewButton, decorators: [{
|
|
851
|
+
type: Component,
|
|
852
|
+
args: [{ template: '' }]
|
|
853
|
+
}], ctorParameters: () => [] });
|
|
854
|
+
|
|
855
|
+
class BackendFormValidationHelper {
|
|
856
|
+
static validateAllFormFields(httpErrorResponse, formGroup) {
|
|
857
|
+
if (!httpErrorResponse || !formGroup) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
// Error 400 = Validation issues
|
|
861
|
+
if (httpErrorResponse.status === 400 && httpErrorResponse.error && httpErrorResponse.error.errors) {
|
|
862
|
+
const controls = Object.keys(formGroup.controls);
|
|
863
|
+
const validationErrors = httpErrorResponse.error.errors;
|
|
864
|
+
Object.keys(validationErrors).forEach((fieldName) => {
|
|
865
|
+
setTimeout(() => {
|
|
866
|
+
const controlName = controls.find((controlName) => controlName.toLocaleLowerCase() === fieldName.toLocaleLowerCase());
|
|
867
|
+
if (!controlName || !formGroup.get(controlName)) {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
formGroup.get(controlName).setErrors({ [validationErrors[fieldName][0]]: true });
|
|
871
|
+
formGroup.get(controlName).markAsTouched();
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
class RouteHelper {
|
|
879
|
+
static getRouteByData(routeSnapshot, key, value) {
|
|
880
|
+
if (!routeSnapshot) {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
return routeSnapshot.data[key] === value
|
|
884
|
+
? routeSnapshot
|
|
885
|
+
: this.getRouteByData(routeSnapshot.firstChild, key, value);
|
|
886
|
+
}
|
|
887
|
+
static getRouteWithComponent(routeSnapshot, component) {
|
|
888
|
+
if (!routeSnapshot) {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
return routeSnapshot.component === component
|
|
892
|
+
? routeSnapshot
|
|
893
|
+
: this.getRouteWithComponent(routeSnapshot.firstChild, component);
|
|
894
|
+
}
|
|
895
|
+
static getRouteURL(route, rootLevel = false) {
|
|
896
|
+
let segments = [];
|
|
897
|
+
const buildRoute = (route) => {
|
|
898
|
+
if (route) {
|
|
899
|
+
if (route.url.length) {
|
|
900
|
+
segments.push(...route.url.map((segment) => segment.path));
|
|
901
|
+
}
|
|
902
|
+
buildRoute(rootLevel ? route.firstChild : route.parent);
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
buildRoute(route);
|
|
906
|
+
if (!rootLevel) {
|
|
907
|
+
segments = segments.reverse();
|
|
908
|
+
}
|
|
909
|
+
return `/${segments.join('/')}`;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
class ConfirmModalComponent {
|
|
914
|
+
//#region ViewChilds, Inputs, Outputs
|
|
915
|
+
modal = viewChild(ModalComponent, /* @ts-ignore */
|
|
916
|
+
...(ngDevMode ? [{ debugName: "modal" }] : /* istanbul ignore next */ []));
|
|
917
|
+
// Models rather than inputs: this component writes both while confirming, from callbacks a
|
|
918
|
+
// parent owns. As plain fields they landed with nothing marking this view dirty.
|
|
919
|
+
errorMessage = model('', /* @ts-ignore */
|
|
920
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
921
|
+
isLoading = model(false, /* @ts-ignore */
|
|
922
|
+
...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
923
|
+
message = input('', /* @ts-ignore */
|
|
924
|
+
...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
925
|
+
messageIcon = input('fa-regular fa-circle-question', /* @ts-ignore */
|
|
926
|
+
...(ngDevMode ? [{ debugName: "messageIcon" }] : /* istanbul ignore next */ []));
|
|
927
|
+
messageIconColor = input('text-cyan-500', /* @ts-ignore */
|
|
928
|
+
...(ngDevMode ? [{ debugName: "messageIconColor" }] : /* istanbul ignore next */ []));
|
|
929
|
+
showMessageIcon = input(true, /* @ts-ignore */
|
|
930
|
+
...(ngDevMode ? [{ debugName: "showMessageIcon" }] : /* istanbul ignore next */ []));
|
|
931
|
+
size = input('xl', /* @ts-ignore */
|
|
932
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
933
|
+
title = input('', /* @ts-ignore */
|
|
934
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region Variables
|
|
937
|
+
formService = inject(FormService, { optional: true }) ?? undefined;
|
|
938
|
+
//#endregion
|
|
939
|
+
//#region Properties
|
|
940
|
+
/**
|
|
941
|
+
* Delegates to the inner modal. A signal view query, not @ViewChild: a computed reading a plain
|
|
942
|
+
* query would never re-run when the query resolves, so the wrapper would answer against an
|
|
943
|
+
* undefined child for ever.
|
|
944
|
+
*/
|
|
945
|
+
isOpen = computed(() => this.modal()?.isOpen() ?? false, /* @ts-ignore */
|
|
946
|
+
...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
947
|
+
get hasErrorMessage() {
|
|
948
|
+
return !!this.errorMessage() && this.errorMessage().length > 0;
|
|
949
|
+
}
|
|
950
|
+
get hasMessage() {
|
|
951
|
+
return !!this.message() && this.message().length > 0;
|
|
952
|
+
}
|
|
953
|
+
controlContainer = inject(ControlContainer, { optional: true });
|
|
954
|
+
get formGroup() {
|
|
955
|
+
return this.controlContainer?.control ?? null;
|
|
956
|
+
}
|
|
957
|
+
//#endregion
|
|
958
|
+
//#region Constructor and Angular life cycle methods
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region Event handlers
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region Public methods
|
|
963
|
+
beginLoading() {
|
|
964
|
+
this.clearErrorMessage();
|
|
965
|
+
this.isLoading.set(true);
|
|
966
|
+
}
|
|
967
|
+
clearErrorMessage() {
|
|
968
|
+
this.errorMessage.set('');
|
|
969
|
+
}
|
|
970
|
+
closeModal() {
|
|
971
|
+
this.modal()?.closeModal();
|
|
972
|
+
}
|
|
973
|
+
finishLoading(closeModal) {
|
|
974
|
+
this.isLoading.set(false);
|
|
975
|
+
if (this.formGroup && this.formGroup.disabled) {
|
|
976
|
+
this.formGroup.enable();
|
|
977
|
+
}
|
|
978
|
+
if (closeModal) {
|
|
979
|
+
this.closeModal();
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
setErrorMessage(httpErrorResponse) {
|
|
983
|
+
this.finishLoading();
|
|
984
|
+
if (httpErrorResponse.status === 400 && !!this.formGroup) {
|
|
985
|
+
BackendFormValidationHelper.validateAllFormFields(httpErrorResponse, this.formGroup);
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
if (typeof httpErrorResponse.error === 'string') {
|
|
989
|
+
this.errorMessage.set(httpErrorResponse.error);
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
this.errorMessage.set('Modal-Failed-DefaultMessage');
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
toggleModal() {
|
|
997
|
+
if (!!this.formService && this.formService.loading) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (this.formGroup) {
|
|
1001
|
+
// TODO: TEST THIS CHANGE
|
|
1002
|
+
this.formGroup.reset();
|
|
1003
|
+
this.formService?.resetForm();
|
|
1004
|
+
}
|
|
1005
|
+
this.clearErrorMessage();
|
|
1006
|
+
this.modal()?.toggleModal();
|
|
1007
|
+
}
|
|
1008
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ConfirmModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1009
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ConfirmModalComponent, isStandalone: true, selector: "framework-confirm-modal", inputs: { errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, messageIcon: { classPropertyName: "messageIcon", publicName: "messageIcon", isSignal: true, isRequired: false, transformFunction: null }, messageIconColor: { classPropertyName: "messageIconColor", publicName: "messageIconColor", isSignal: true, isRequired: false, transformFunction: null }, showMessageIcon: { classPropertyName: "showMessageIcon", publicName: "showMessageIcon", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { errorMessage: "errorMessageChange", isLoading: "isLoadingChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: ModalComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<lib-modal\r\n position=\"top\"\r\n [closeButtonText]=\"'Button-Modal-Cancel' | translate\"\r\n [size]=\"size()\"\r\n [title]=\"title() | translate\"\r\n>\r\n <div body>\r\n @if (hasMessage) {\r\n <div class=\"message\">\r\n @if (showMessageIcon()) {\r\n <i class=\"fa-3x\" [ngClass]=\"[messageIcon(), messageIconColor()]\"></i>\r\n }\r\n <h4 [innerHTML]=\"message() | translate\"></h4>\r\n </div>\r\n }\r\n\r\n <ng-content></ng-content>\r\n\r\n @if (hasErrorMessage) {\r\n <div class=\"erro\">\r\n <h4>{{ errorMessage() | translate }}</h4>\r\n <span>{{ 'Modal-Failed-Administrator' | translate }}</span>\r\n </div>\r\n }\r\n </div>\r\n <div footer>\r\n <ng-content select=\"[footer]\"></ng-content>\r\n </div>\r\n</lib-modal>\r\n", styles: ["@layer properties;:host{display:contents}.message{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}.message h4{flex-grow:1;text-align:justify}.erro{margin-inline:calc(var(--spacing, .25rem) * -4);margin-top:calc(var(--spacing, .25rem) * 3);margin-bottom:calc(var(--spacing, .25rem) * -4);padding:calc(var(--spacing, .25rem) * 4);text-align:center;border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--color-gray-300, oklch(87.2% .01 258.338));background-color:var(--color-red-500, oklch(63.7% .237 25.331));color:var(--color-white, #fff)}.erro h4{--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700)}.erro span{margin-top:var(--spacing, .25rem);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "lib-modal", inputs: ["closeButtonText", "dialog", "modalProcessing", "position", "size", "title"], outputs: ["closeButtonTextChange", "modalProcessingChange", "closed"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1010
|
+
}
|
|
1011
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ConfirmModalComponent, decorators: [{
|
|
1012
|
+
type: Component,
|
|
1013
|
+
args: [{ selector: 'framework-confirm-modal', imports: [ModalComponent, NgClass, TranslatePipe], template: "<lib-modal\r\n position=\"top\"\r\n [closeButtonText]=\"'Button-Modal-Cancel' | translate\"\r\n [size]=\"size()\"\r\n [title]=\"title() | translate\"\r\n>\r\n <div body>\r\n @if (hasMessage) {\r\n <div class=\"message\">\r\n @if (showMessageIcon()) {\r\n <i class=\"fa-3x\" [ngClass]=\"[messageIcon(), messageIconColor()]\"></i>\r\n }\r\n <h4 [innerHTML]=\"message() | translate\"></h4>\r\n </div>\r\n }\r\n\r\n <ng-content></ng-content>\r\n\r\n @if (hasErrorMessage) {\r\n <div class=\"erro\">\r\n <h4>{{ errorMessage() | translate }}</h4>\r\n <span>{{ 'Modal-Failed-Administrator' | translate }}</span>\r\n </div>\r\n }\r\n </div>\r\n <div footer>\r\n <ng-content select=\"[footer]\"></ng-content>\r\n </div>\r\n</lib-modal>\r\n", styles: ["@layer properties;:host{display:contents}.message{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}.message h4{flex-grow:1;text-align:justify}.erro{margin-inline:calc(var(--spacing, .25rem) * -4);margin-top:calc(var(--spacing, .25rem) * 3);margin-bottom:calc(var(--spacing, .25rem) * -4);padding:calc(var(--spacing, .25rem) * 4);text-align:center;border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--color-gray-300, oklch(87.2% .01 258.338));background-color:var(--color-red-500, oklch(63.7% .237 25.331));color:var(--color-white, #fff)}.erro h4{--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700)}.erro span{margin-top:var(--spacing, .25rem);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
1014
|
+
}], propDecorators: { modal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ModalComponent), { isSignal: true }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }, { type: i0.Output, args: ["errorMessageChange"] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }, { type: i0.Output, args: ["isLoadingChange"] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], messageIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageIcon", required: false }] }], messageIconColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageIconColor", required: false }] }], showMessageIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMessageIcon", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }] } });
|
|
1015
|
+
|
|
1016
|
+
class ErrorModalComponent {
|
|
1017
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1018
|
+
modal = viewChild(ModalComponent, /* @ts-ignore */
|
|
1019
|
+
...(ngDevMode ? [{ debugName: "modal" }] : /* istanbul ignore next */ []));
|
|
1020
|
+
// A model rather than an input: showModal() writes it, and a parent calls that from its own
|
|
1021
|
+
// subscription. As a plain field the message landed with nothing marking this view dirty.
|
|
1022
|
+
errorMessage = model('', /* @ts-ignore */
|
|
1023
|
+
...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
1024
|
+
size = input('xl', /* @ts-ignore */
|
|
1025
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
1026
|
+
// Models too: DefaultDetailsTabViewComponent sets both before showing the modal, so that a 404
|
|
1027
|
+
// reads as "record not found" rather than as a server error.
|
|
1028
|
+
subtitle = model('Modal-Failed-Administrator', /* @ts-ignore */
|
|
1029
|
+
...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
|
|
1030
|
+
title = model('Modal-Failed-Title', /* @ts-ignore */
|
|
1031
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
1032
|
+
closed = output();
|
|
1033
|
+
//#endregion
|
|
1034
|
+
//#region Variables
|
|
1035
|
+
//#endregion
|
|
1036
|
+
//#region Properties
|
|
1037
|
+
/**
|
|
1038
|
+
* Delegates to the inner modal. A signal view query, not @ViewChild: a computed reading a plain
|
|
1039
|
+
* query would never re-run when the query resolves, so the wrapper would answer against an
|
|
1040
|
+
* undefined child for ever.
|
|
1041
|
+
*/
|
|
1042
|
+
isOpen = computed(() => this.modal()?.isOpen() ?? false, /* @ts-ignore */
|
|
1043
|
+
...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
1044
|
+
//#endregion
|
|
1045
|
+
//#region Constructor and Angular life cycle methods
|
|
1046
|
+
//#endregion
|
|
1047
|
+
//#region Event handlers
|
|
1048
|
+
//#endregion
|
|
1049
|
+
//#region Public methods
|
|
1050
|
+
closeModal() {
|
|
1051
|
+
this.modal()?.closeModal();
|
|
1052
|
+
}
|
|
1053
|
+
showModal(error) {
|
|
1054
|
+
let errorMessage = 'Modal-Failed-DefaultMessage';
|
|
1055
|
+
if (typeof error === 'string') {
|
|
1056
|
+
errorMessage = error;
|
|
1057
|
+
}
|
|
1058
|
+
else if (typeof error.error === 'string') {
|
|
1059
|
+
errorMessage = error.error;
|
|
1060
|
+
}
|
|
1061
|
+
this.errorMessage.set(errorMessage);
|
|
1062
|
+
this.modal()?.toggleModal();
|
|
1063
|
+
}
|
|
1064
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1065
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ErrorModalComponent, isStandalone: true, selector: "framework-error-modal", inputs: { errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { errorMessage: "errorMessageChange", subtitle: "subtitleChange", title: "titleChange", closed: "closed" }, viewQueries: [{ propertyName: "modal", first: true, predicate: ModalComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<lib-modal\r\n position=\"top\"\r\n [closeButtonText]=\"'Button-Modal-Close' | translate\"\r\n [size]=\"size()\"\r\n [title]=\"title() | translate\"\r\n (closed)=\"closed.emit()\"\r\n>\r\n <div body>\r\n <div class=\"erro\">\r\n <h4>{{ errorMessage() | translate }}</h4>\r\n @if (subtitle()) {\r\n <span>{{ subtitle() | translate }}</span>\r\n }\r\n </div>\r\n </div>\r\n</lib-modal>\r\n", styles: ["@layer properties;:host{display:contents}.erro{margin-inline:calc(var(--spacing, .25rem) * -4);margin-top:-1.1rem;margin-bottom:calc(var(--spacing, .25rem) * -4);padding-block:calc(var(--spacing, .25rem) * 4);text-align:center;border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--color-gray-300, oklch(87.2% .01 258.338));background-color:var(--color-red-500, oklch(63.7% .237 25.331));color:var(--color-white, #fff)}.erro h4{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700);white-space:pre-line}.erro span{margin-top:var(--spacing, .25rem);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "lib-modal", inputs: ["closeButtonText", "dialog", "modalProcessing", "position", "size", "title"], outputs: ["closeButtonTextChange", "modalProcessingChange", "closed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1066
|
+
}
|
|
1067
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ErrorModalComponent, decorators: [{
|
|
1068
|
+
type: Component,
|
|
1069
|
+
args: [{ selector: 'framework-error-modal', imports: [ModalComponent, TranslatePipe], template: "<lib-modal\r\n position=\"top\"\r\n [closeButtonText]=\"'Button-Modal-Close' | translate\"\r\n [size]=\"size()\"\r\n [title]=\"title() | translate\"\r\n (closed)=\"closed.emit()\"\r\n>\r\n <div body>\r\n <div class=\"erro\">\r\n <h4>{{ errorMessage() | translate }}</h4>\r\n @if (subtitle()) {\r\n <span>{{ subtitle() | translate }}</span>\r\n }\r\n </div>\r\n </div>\r\n</lib-modal>\r\n", styles: ["@layer properties;:host{display:contents}.erro{margin-inline:calc(var(--spacing, .25rem) * -4);margin-top:-1.1rem;margin-bottom:calc(var(--spacing, .25rem) * -4);padding-block:calc(var(--spacing, .25rem) * 4);text-align:center;border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--color-gray-300, oklch(87.2% .01 258.338));background-color:var(--color-red-500, oklch(63.7% .237 25.331));color:var(--color-white, #fff)}.erro h4{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700);white-space:pre-line}.erro span{margin-top:var(--spacing, .25rem);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
1070
|
+
}], propDecorators: { modal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ModalComponent), { isSignal: true }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }, { type: i0.Output, args: ["errorMessageChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }, { type: i0.Output, args: ["subtitleChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }, { type: i0.Output, args: ["titleChange"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* @deprecated Use standalone component imports instead of FrameworkModalsModule.
|
|
1074
|
+
* Import individual components directly in your component's `imports` array.
|
|
1075
|
+
*/
|
|
1076
|
+
class FrameworkModalsModule {
|
|
1077
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModalsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1078
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModalsModule, imports: [ConfirmModalComponent, ErrorModalComponent], exports: [ConfirmModalComponent, ErrorModalComponent] });
|
|
1079
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModalsModule, imports: [ConfirmModalComponent, ErrorModalComponent] });
|
|
1080
|
+
}
|
|
1081
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModalsModule, decorators: [{
|
|
1082
|
+
type: NgModule,
|
|
1083
|
+
args: [{
|
|
1084
|
+
declarations: [],
|
|
1085
|
+
imports: [ConfirmModalComponent, ErrorModalComponent],
|
|
1086
|
+
exports: [ConfirmModalComponent, ErrorModalComponent],
|
|
1087
|
+
}]
|
|
1088
|
+
}] });
|
|
1089
|
+
|
|
1090
|
+
class ButtonConfirmComponent extends BaseButton {
|
|
1091
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1092
|
+
confirmModal = viewChild(ConfirmModalComponent, /* @ts-ignore */
|
|
1093
|
+
...(ngDevMode ? [{ debugName: "confirmModal" }] : /* istanbul ignore next */ []));
|
|
1094
|
+
action = input.required(/* @ts-ignore */
|
|
1095
|
+
...(ngDevMode ? [{ debugName: "action" }] : /* istanbul ignore next */ []));
|
|
1096
|
+
color = input('text-primary-500', /* @ts-ignore */
|
|
1097
|
+
...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
1098
|
+
icon = input('', /* @ts-ignore */
|
|
1099
|
+
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
1100
|
+
label = input('', /* @ts-ignore */
|
|
1101
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1102
|
+
modalConfirmButtonColor = input('', /* @ts-ignore */
|
|
1103
|
+
...(ngDevMode ? [{ debugName: "modalConfirmButtonColor" }] : /* istanbul ignore next */ []));
|
|
1104
|
+
modalConfirmButtonLabel = input('', /* @ts-ignore */
|
|
1105
|
+
...(ngDevMode ? [{ debugName: "modalConfirmButtonLabel" }] : /* istanbul ignore next */ []));
|
|
1106
|
+
modalMessage = input('', /* @ts-ignore */
|
|
1107
|
+
...(ngDevMode ? [{ debugName: "modalMessage" }] : /* istanbul ignore next */ []));
|
|
1108
|
+
modalMessageIcon = input('fa-solid fa-circle-question', /* @ts-ignore */
|
|
1109
|
+
...(ngDevMode ? [{ debugName: "modalMessageIcon" }] : /* istanbul ignore next */ []));
|
|
1110
|
+
modalMessageIconColor = input('', /* @ts-ignore */
|
|
1111
|
+
...(ngDevMode ? [{ debugName: "modalMessageIconColor" }] : /* istanbul ignore next */ []));
|
|
1112
|
+
modalTitle = input('', /* @ts-ignore */
|
|
1113
|
+
...(ngDevMode ? [{ debugName: "modalTitle" }] : /* istanbul ignore next */ []));
|
|
1114
|
+
//#endregion
|
|
1115
|
+
//#region Variables
|
|
1116
|
+
gridDataset = inject(DataGridDataset);
|
|
1117
|
+
//#endregion
|
|
1118
|
+
//#region Properties
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region Constructor and Angular life cycle methods
|
|
1121
|
+
constructor() {
|
|
1122
|
+
super();
|
|
1123
|
+
this.disabled.set(true);
|
|
1124
|
+
}
|
|
1125
|
+
//#endregion
|
|
1126
|
+
//#region Event handlers
|
|
1127
|
+
onButtonClicked() {
|
|
1128
|
+
this.confirmModal()?.toggleModal();
|
|
1129
|
+
}
|
|
1130
|
+
onDelete() {
|
|
1131
|
+
const confirmModal = this.confirmModal();
|
|
1132
|
+
if (!confirmModal || confirmModal.isLoading() || !confirmModal.isOpen()) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
confirmModal.beginLoading();
|
|
1136
|
+
this.action()
|
|
1137
|
+
.pipe(take(1))
|
|
1138
|
+
.subscribe({
|
|
1139
|
+
next: () => {
|
|
1140
|
+
this.finishLoading('success');
|
|
1141
|
+
confirmModal.finishLoading(true);
|
|
1142
|
+
this.gridDataset.refresh();
|
|
1143
|
+
},
|
|
1144
|
+
error: (e) => {
|
|
1145
|
+
confirmModal.setErrorMessage(e);
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonConfirmComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1150
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonConfirmComponent, isStandalone: true, selector: "framework-button-confirm", inputs: { action: { classPropertyName: "action", publicName: "action", isSignal: true, isRequired: true, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, modalConfirmButtonColor: { classPropertyName: "modalConfirmButtonColor", publicName: "modalConfirmButtonColor", isSignal: true, isRequired: false, transformFunction: null }, modalConfirmButtonLabel: { classPropertyName: "modalConfirmButtonLabel", publicName: "modalConfirmButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, modalMessage: { classPropertyName: "modalMessage", publicName: "modalMessage", isSignal: true, isRequired: false, transformFunction: null }, modalMessageIcon: { classPropertyName: "modalMessageIcon", publicName: "modalMessageIcon", isSignal: true, isRequired: false, transformFunction: null }, modalMessageIconColor: { classPropertyName: "modalMessageIconColor", publicName: "modalMessageIconColor", isSignal: true, isRequired: false, transformFunction: null }, modalTitle: { classPropertyName: "modalTitle", publicName: "modalTitle", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonConfirmComponent) }], viewQueries: [{ propertyName: "confirmModal", first: true, predicate: ConfirmModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [color]=\"color()\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"icon()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-confirm-modal\r\n #modal\r\n [message]=\"modalMessage()\"\r\n [messageIcon]=\"modalMessageIcon()\"\r\n [messageIconColor]=\"modalMessageIconColor()\"\r\n [title]=\"modalTitle()\"\r\n>\r\n <div footer>\r\n <button\r\n class=\"btn {{ modalConfirmButtonColor() }}\"\r\n type=\"button\"\r\n [class.loading]=\"modal.isLoading()\"\r\n (click)=\"onDelete()\"\r\n >\r\n {{ modalConfirmButtonLabel() | translate }}\r\n </button>\r\n </div>\r\n</framework-confirm-modal>\r\n", dependencies: [{ kind: "component", type: ConfirmModalComponent, selector: "framework-confirm-modal", inputs: ["errorMessage", "isLoading", "message", "messageIcon", "messageIconColor", "showMessageIcon", "size", "title"], outputs: ["errorMessageChange", "isLoadingChange"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1151
|
+
}
|
|
1152
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonConfirmComponent, decorators: [{
|
|
1153
|
+
type: Component,
|
|
1154
|
+
args: [{ selector: 'framework-button-confirm', imports: [ConfirmModalComponent, RibbonButtonComponent, TranslatePipe], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonConfirmComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [color]=\"color()\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"icon()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-confirm-modal\r\n #modal\r\n [message]=\"modalMessage()\"\r\n [messageIcon]=\"modalMessageIcon()\"\r\n [messageIconColor]=\"modalMessageIconColor()\"\r\n [title]=\"modalTitle()\"\r\n>\r\n <div footer>\r\n <button\r\n class=\"btn {{ modalConfirmButtonColor() }}\"\r\n type=\"button\"\r\n [class.loading]=\"modal.isLoading()\"\r\n (click)=\"onDelete()\"\r\n >\r\n {{ modalConfirmButtonLabel() | translate }}\r\n </button>\r\n </div>\r\n</framework-confirm-modal>\r\n" }]
|
|
1155
|
+
}], ctorParameters: () => [], propDecorators: { confirmModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ConfirmModalComponent), { isSignal: true }] }], action: [{ type: i0.Input, args: [{ isSignal: true, alias: "action", required: true }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], modalConfirmButtonColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalConfirmButtonColor", required: false }] }], modalConfirmButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalConfirmButtonLabel", required: false }] }], modalMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalMessage", required: false }] }], modalMessageIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalMessageIcon", required: false }] }], modalMessageIconColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalMessageIconColor", required: false }] }], modalTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalTitle", required: false }] }] } });
|
|
1156
|
+
|
|
1157
|
+
class ButtonDeleteComponent extends BaseButton {
|
|
1158
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1159
|
+
confirmModal = viewChild(ConfirmModalComponent, /* @ts-ignore */
|
|
1160
|
+
...(ngDevMode ? [{ debugName: "confirmModal" }] : /* istanbul ignore next */ []));
|
|
1161
|
+
action = input.required(/* @ts-ignore */
|
|
1162
|
+
...(ngDevMode ? [{ debugName: "action" }] : /* istanbul ignore next */ []));
|
|
1163
|
+
label = input('Button-Delete', /* @ts-ignore */
|
|
1164
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1165
|
+
modalConfirmButtonLabel = input('Modal-Delete-Confirm', /* @ts-ignore */
|
|
1166
|
+
...(ngDevMode ? [{ debugName: "modalConfirmButtonLabel" }] : /* istanbul ignore next */ []));
|
|
1167
|
+
modalMessage = input('Modal-Delete-Message', /* @ts-ignore */
|
|
1168
|
+
...(ngDevMode ? [{ debugName: "modalMessage" }] : /* istanbul ignore next */ []));
|
|
1169
|
+
modalTitle = input('Modal-Delete-Title', /* @ts-ignore */
|
|
1170
|
+
...(ngDevMode ? [{ debugName: "modalTitle" }] : /* istanbul ignore next */ []));
|
|
1171
|
+
//#endregion
|
|
1172
|
+
//#region Variables
|
|
1173
|
+
gridDataset = inject(DataGridDataset);
|
|
1174
|
+
//#endregion
|
|
1175
|
+
//#region Properties
|
|
1176
|
+
//#endregion
|
|
1177
|
+
//#region Constructor and Angular life cycle methods
|
|
1178
|
+
constructor() {
|
|
1179
|
+
super();
|
|
1180
|
+
this.disabled.set(true);
|
|
1181
|
+
}
|
|
1182
|
+
//#endregion
|
|
1183
|
+
//#region Event handlers
|
|
1184
|
+
onButtonClicked() {
|
|
1185
|
+
this.confirmModal()?.toggleModal();
|
|
1186
|
+
}
|
|
1187
|
+
onDelete() {
|
|
1188
|
+
const confirmModal = this.confirmModal();
|
|
1189
|
+
if (!confirmModal || confirmModal.isLoading() || !confirmModal.isOpen()) {
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
confirmModal.beginLoading();
|
|
1193
|
+
this.action()
|
|
1194
|
+
.pipe(take(1))
|
|
1195
|
+
.subscribe({
|
|
1196
|
+
next: () => {
|
|
1197
|
+
this.finishLoading('success');
|
|
1198
|
+
confirmModal.finishLoading(true);
|
|
1199
|
+
this.gridDataset.refresh();
|
|
1200
|
+
},
|
|
1201
|
+
error: (e) => {
|
|
1202
|
+
confirmModal.setErrorMessage(e);
|
|
1203
|
+
},
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonDeleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1207
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonDeleteComponent, isStandalone: true, selector: "framework-button-delete", inputs: { action: { classPropertyName: "action", publicName: "action", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, modalConfirmButtonLabel: { classPropertyName: "modalConfirmButtonLabel", publicName: "modalConfirmButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, modalMessage: { classPropertyName: "modalMessage", publicName: "modalMessage", isSignal: true, isRequired: false, transformFunction: null }, modalTitle: { classPropertyName: "modalTitle", publicName: "modalTitle", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonDeleteComponent) }], viewQueries: [{ propertyName: "confirmModal", first: true, predicate: ConfirmModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n color=\"text-red-500\"\r\n icon=\"fa-trash-alt\"\r\n [disabled]=\"disabled()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-confirm-modal\r\n #modal\r\n messageIcon=\"fa-solid fa-circle-exclamation\"\r\n messageIconColor=\"text-red-500\"\r\n [message]=\"modalMessage()\"\r\n [title]=\"modalTitle()\"\r\n>\r\n <div footer>\r\n <button class=\"btn red-500\" type=\"button\" [class.loading]=\"modal.isLoading()\" (click)=\"onDelete()\">\r\n {{ modalConfirmButtonLabel() | translate }}\r\n </button>\r\n </div>\r\n</framework-confirm-modal>\r\n", dependencies: [{ kind: "component", type: ConfirmModalComponent, selector: "framework-confirm-modal", inputs: ["errorMessage", "isLoading", "message", "messageIcon", "messageIconColor", "showMessageIcon", "size", "title"], outputs: ["errorMessageChange", "isLoadingChange"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1208
|
+
}
|
|
1209
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonDeleteComponent, decorators: [{
|
|
1210
|
+
type: Component,
|
|
1211
|
+
args: [{ selector: 'framework-button-delete', imports: [ConfirmModalComponent, RibbonButtonComponent, TranslatePipe], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonDeleteComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n color=\"text-red-500\"\r\n icon=\"fa-trash-alt\"\r\n [disabled]=\"disabled()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-confirm-modal\r\n #modal\r\n messageIcon=\"fa-solid fa-circle-exclamation\"\r\n messageIconColor=\"text-red-500\"\r\n [message]=\"modalMessage()\"\r\n [title]=\"modalTitle()\"\r\n>\r\n <div footer>\r\n <button class=\"btn red-500\" type=\"button\" [class.loading]=\"modal.isLoading()\" (click)=\"onDelete()\">\r\n {{ modalConfirmButtonLabel() | translate }}\r\n </button>\r\n </div>\r\n</framework-confirm-modal>\r\n" }]
|
|
1212
|
+
}], ctorParameters: () => [], propDecorators: { confirmModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ConfirmModalComponent), { isSignal: true }] }], action: [{ type: i0.Input, args: [{ isSignal: true, alias: "action", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], modalConfirmButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalConfirmButtonLabel", required: false }] }], modalMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalMessage", required: false }] }], modalTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalTitle", required: false }] }] } });
|
|
1213
|
+
|
|
1214
|
+
class ButtonEditComponent extends BaseButton {
|
|
1215
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1216
|
+
cancelColor = input('text-red-500', /* @ts-ignore */
|
|
1217
|
+
...(ngDevMode ? [{ debugName: "cancelColor" }] : /* istanbul ignore next */ []));
|
|
1218
|
+
cancelIcon = input('fa-ban', /* @ts-ignore */
|
|
1219
|
+
...(ngDevMode ? [{ debugName: "cancelIcon" }] : /* istanbul ignore next */ []));
|
|
1220
|
+
cancelLabel = input('Button-Cancel-Edit', /* @ts-ignore */
|
|
1221
|
+
...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
1222
|
+
editIcon = input('fa-edit', /* @ts-ignore */
|
|
1223
|
+
...(ngDevMode ? [{ debugName: "editIcon" }] : /* istanbul ignore next */ []));
|
|
1224
|
+
editLabel = input('Button-Edit', /* @ts-ignore */
|
|
1225
|
+
...(ngDevMode ? [{ debugName: "editLabel" }] : /* istanbul ignore next */ []));
|
|
1226
|
+
modal = input(/* @ts-ignore */
|
|
1227
|
+
...(ngDevMode ? [undefined, { debugName: "modal" }] : /* istanbul ignore next */ []));
|
|
1228
|
+
//#endregion
|
|
1229
|
+
//#region Variables
|
|
1230
|
+
// A signal rather than a field: the form cancelling an edit flips this from a subscription,
|
|
1231
|
+
// which swaps Edit back in for Cancel.
|
|
1232
|
+
isButtonCancelVisible = signal(false, /* @ts-ignore */
|
|
1233
|
+
...(ngDevMode ? [{ debugName: "isButtonCancelVisible" }] : /* istanbul ignore next */ []));
|
|
1234
|
+
dataProviderService = inject(DataProviderService);
|
|
1235
|
+
formService = inject(FormService);
|
|
1236
|
+
//#endregion
|
|
1237
|
+
//#region Properties
|
|
1238
|
+
get hasDataProviderEntityID() {
|
|
1239
|
+
return this.dataProviderService?.hasEntityID ?? false;
|
|
1240
|
+
}
|
|
1241
|
+
get isFormLoading() {
|
|
1242
|
+
return this.formService.loading;
|
|
1243
|
+
}
|
|
1244
|
+
//#endregion
|
|
1245
|
+
//#region Constructor and Angular life cycle methods
|
|
1246
|
+
ngOnInit() {
|
|
1247
|
+
this.formService.editCanceled.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
1248
|
+
this.isButtonCancelVisible.set(false);
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
//#endregion
|
|
1252
|
+
//#region Event handlers
|
|
1253
|
+
onCancelClicked() {
|
|
1254
|
+
if (this.isButtonCancelVisible()) {
|
|
1255
|
+
this.isButtonCancelVisible.set(false);
|
|
1256
|
+
this.formService.cancelEdit();
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
onEditClicked() {
|
|
1260
|
+
const modal = this.modal();
|
|
1261
|
+
if (modal) {
|
|
1262
|
+
modal.toggle();
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
this.isButtonCancelVisible.set(true);
|
|
1266
|
+
this.formService.beginEdit();
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1270
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonEditComponent, isStandalone: true, selector: "framework-button-edit", inputs: { cancelColor: { classPropertyName: "cancelColor", publicName: "cancelColor", isSignal: true, isRequired: false, transformFunction: null }, cancelIcon: { classPropertyName: "cancelIcon", publicName: "cancelIcon", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, editIcon: { classPropertyName: "editIcon", publicName: "editIcon", isSignal: true, isRequired: false, transformFunction: null }, editLabel: { classPropertyName: "editLabel", publicName: "editLabel", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonEditComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible() && !isButtonCancelVisible() && hasDataProviderEntityID) {\r\n <lib-ribbon-button\r\n [disabled]=\"disabled() || !isAccessLoaded() || isFormLoading\"\r\n [icon]=\"editIcon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"editLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onEditClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n@if (isVisible() && isButtonCancelVisible() && hasDataProviderEntityID) {\r\n <lib-ribbon-button\r\n [color]=\"cancelColor()\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || isFormLoading\"\r\n [icon]=\"cancelIcon()\"\r\n [label]=\"cancelLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onCancelClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
1271
|
+
}
|
|
1272
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonEditComponent, decorators: [{
|
|
1273
|
+
type: Component,
|
|
1274
|
+
args: [{ selector: 'framework-button-edit', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonEditComponent) }], template: "@if (isVisible() && !isButtonCancelVisible() && hasDataProviderEntityID) {\r\n <lib-ribbon-button\r\n [disabled]=\"disabled() || !isAccessLoaded() || isFormLoading\"\r\n [icon]=\"editIcon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"editLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onEditClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n@if (isVisible() && isButtonCancelVisible() && hasDataProviderEntityID) {\r\n <lib-ribbon-button\r\n [color]=\"cancelColor()\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || isFormLoading\"\r\n [icon]=\"cancelIcon()\"\r\n [label]=\"cancelLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onCancelClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
|
|
1275
|
+
}], propDecorators: { cancelColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelColor", required: false }] }], cancelIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelIcon", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], editIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "editIcon", required: false }] }], editLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "editLabel", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }] } });
|
|
1276
|
+
|
|
1277
|
+
const MaxExportRows = 50000;
|
|
1278
|
+
class ButtonExportComponent extends BaseButton {
|
|
1279
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1280
|
+
/**
|
|
1281
|
+
* Index into `options` exported when the main button is clicked, defaulting to Excel.
|
|
1282
|
+
* `RibbonButtonComponent` treats -1 as "no default" and merely toggles the dropdown, which left
|
|
1283
|
+
* Export needing two clicks for the common case while Save (which sets 0) needed one. Set -1 to
|
|
1284
|
+
* restore the dropdown-only behaviour.
|
|
1285
|
+
*/
|
|
1286
|
+
defaultOption = input(0, /* @ts-ignore */
|
|
1287
|
+
...(ngDevMode ? [{ debugName: "defaultOption" }] : /* istanbul ignore next */ []));
|
|
1288
|
+
fileBaseName = input('export', /* @ts-ignore */
|
|
1289
|
+
...(ngDevMode ? [{ debugName: "fileBaseName" }] : /* istanbul ignore next */ []));
|
|
1290
|
+
//#endregion
|
|
1291
|
+
//#region Variables
|
|
1292
|
+
dataGridDataset = inject(DataGridDataset, { optional: true });
|
|
1293
|
+
translateService = inject(TranslateService);
|
|
1294
|
+
//#endregion
|
|
1295
|
+
//#region Properties
|
|
1296
|
+
get isButtonDisabled() {
|
|
1297
|
+
return super.isButtonDisabled || this.isExceedingExportLimit;
|
|
1298
|
+
}
|
|
1299
|
+
get tooltip() {
|
|
1300
|
+
if (this.isExceedingExportLimit) {
|
|
1301
|
+
return this.translateService.instant('Button-Export-LimitExceeded', { max: MaxExportRows });
|
|
1302
|
+
}
|
|
1303
|
+
return '';
|
|
1304
|
+
}
|
|
1305
|
+
get isExceedingExportLimit() {
|
|
1306
|
+
const totalRows = this.dataGridDataset?.totalRows;
|
|
1307
|
+
return totalRows !== undefined && totalRows > MaxExportRows;
|
|
1308
|
+
}
|
|
1309
|
+
//#endregion
|
|
1310
|
+
//#region Constructor and Angular life cycle methods
|
|
1311
|
+
constructor() {
|
|
1312
|
+
super();
|
|
1313
|
+
this.options.set([
|
|
1314
|
+
{ id: 'xlsx', label: 'Button-Export-Excel', icon: 'fa-file-excel' },
|
|
1315
|
+
{ id: 'pdf', label: 'Button-Export-PDF', icon: 'fa-file-pdf' },
|
|
1316
|
+
{ id: 'csv', label: 'Button-Export-CSV', icon: 'fa-file-csv' },
|
|
1317
|
+
{ id: 'xml', label: 'Button-Export-XML', icon: 'fa-file-code' },
|
|
1318
|
+
{ id: 'mhtml', label: 'Button-Export-MHTML', icon: 'fa-file-archive' },
|
|
1319
|
+
]);
|
|
1320
|
+
}
|
|
1321
|
+
//#endregion
|
|
1322
|
+
//#region Event handlers
|
|
1323
|
+
onButtonClicked(format) {
|
|
1324
|
+
if (!this.dataGridDataset) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
const resolvedFormat = format ?? 'xlsx';
|
|
1328
|
+
this.startLoading();
|
|
1329
|
+
const parameters = {
|
|
1330
|
+
endRow: 0,
|
|
1331
|
+
filters: this.dataGridDataset.filters,
|
|
1332
|
+
startRow: 0,
|
|
1333
|
+
};
|
|
1334
|
+
this.dataGridDataset
|
|
1335
|
+
.export(resolvedFormat, parameters)
|
|
1336
|
+
.pipe(take(1))
|
|
1337
|
+
.subscribe({
|
|
1338
|
+
next: (response) => {
|
|
1339
|
+
this.download(response);
|
|
1340
|
+
this.finishLoading('success');
|
|
1341
|
+
},
|
|
1342
|
+
error: (_) => {
|
|
1343
|
+
this.finishLoading('failure');
|
|
1344
|
+
},
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
//#endregion
|
|
1348
|
+
//#region Private methods
|
|
1349
|
+
download(response) {
|
|
1350
|
+
if (!response.body) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const contentDisposition = response.headers.get('Content-Disposition') ?? '';
|
|
1354
|
+
const match = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(contentDisposition);
|
|
1355
|
+
const fileName = match ? decodeURIComponent(match[1]) : this.fileBaseName();
|
|
1356
|
+
const url = URL.createObjectURL(response.body);
|
|
1357
|
+
const anchor = document.createElement('a');
|
|
1358
|
+
anchor.href = url;
|
|
1359
|
+
anchor.download = fileName;
|
|
1360
|
+
anchor.click();
|
|
1361
|
+
URL.revokeObjectURL(url);
|
|
1362
|
+
}
|
|
1363
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonExportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1364
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonExportComponent, isStandalone: true, selector: "framework-button-export", inputs: { defaultOption: { classPropertyName: "defaultOption", publicName: "defaultOption", isSignal: true, isRequired: false, transformFunction: null }, fileBaseName: { classPropertyName: "fileBaseName", publicName: "fileBaseName", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonExportComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Export\"\r\n icon=\"fa-file-export\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"isButtonDisabled\"\r\n [loading]=\"loading()\"\r\n [iconSize]=\"iconSize()\"\r\n [options]=\"options()\"\r\n [tooltip]=\"tooltip\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
1365
|
+
}
|
|
1366
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonExportComponent, decorators: [{
|
|
1367
|
+
type: Component,
|
|
1368
|
+
args: [{ selector: 'framework-button-export', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonExportComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Export\"\r\n icon=\"fa-file-export\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"isButtonDisabled\"\r\n [loading]=\"loading()\"\r\n [iconSize]=\"iconSize()\"\r\n [options]=\"options()\"\r\n [tooltip]=\"tooltip\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
|
|
1369
|
+
}], ctorParameters: () => [], propDecorators: { defaultOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOption", required: false }] }], fileBaseName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileBaseName", required: false }] }] } });
|
|
1370
|
+
|
|
1371
|
+
class ButtonFiltersComponent extends BaseButton {
|
|
1372
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1373
|
+
modal = viewChild(ModalComponent, /* @ts-ignore */
|
|
1374
|
+
...(ngDevMode ? [{ debugName: "modal" }] : /* istanbul ignore next */ []));
|
|
1375
|
+
modalSize = input('xl', /* @ts-ignore */
|
|
1376
|
+
...(ngDevMode ? [{ debugName: "modalSize" }] : /* istanbul ignore next */ []));
|
|
1377
|
+
modalTitle = input('', /* @ts-ignore */
|
|
1378
|
+
...(ngDevMode ? [{ debugName: "modalTitle" }] : /* istanbul ignore next */ []));
|
|
1379
|
+
validateFormFunction = input(/* @ts-ignore */
|
|
1380
|
+
...(ngDevMode ? [undefined, { debugName: "validateFormFunction" }] : /* istanbul ignore next */ []));
|
|
1381
|
+
//#endregion
|
|
1382
|
+
//#region Variables
|
|
1383
|
+
gridDataset = inject(DataGridDataset);
|
|
1384
|
+
formGroup = inject(FormGroupDirective);
|
|
1385
|
+
// A signal rather than a field: submitting or clearing the filters changes which buttons the
|
|
1386
|
+
// ribbon shows, and both happen from a click handler deep inside the modal.
|
|
1387
|
+
filters = signal({}, /* @ts-ignore */
|
|
1388
|
+
...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
|
|
1389
|
+
//#endregion
|
|
1390
|
+
//#region Properties
|
|
1391
|
+
get hasFiltersApplied() {
|
|
1392
|
+
return Object.keys(this.filters()).length > 0;
|
|
1393
|
+
}
|
|
1394
|
+
get singleColumn() {
|
|
1395
|
+
const modalSize = this.modalSize();
|
|
1396
|
+
return modalSize === 'sm' || modalSize === 'md' || modalSize === 'lg' || modalSize === 'xl';
|
|
1397
|
+
}
|
|
1398
|
+
//#endregion
|
|
1399
|
+
//#region Constructor and Angular life cycle methods
|
|
1400
|
+
constructor() {
|
|
1401
|
+
super();
|
|
1402
|
+
// This will force the screen to initialize with the filter disabled.
|
|
1403
|
+
this.disabled.set(true);
|
|
1404
|
+
}
|
|
1405
|
+
ngOnInit() {
|
|
1406
|
+
// The constructor disables the button and the two subscriptions below are what re-enable it,
|
|
1407
|
+
// which is right only for a button that exists before its grid first loads. A ribbon template
|
|
1408
|
+
// is re-created whenever a record's views are switched, so a button built then arrives at a
|
|
1409
|
+
// grid that finished loading long ago, waits for an event that has already passed, and stays
|
|
1410
|
+
// unclickable until something makes the grid load again. The dataset knows either way.
|
|
1411
|
+
this.disabled.set(this.gridDataset.isLoading || !this.gridDataset.hasBeenLoaded);
|
|
1412
|
+
this.gridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
1413
|
+
this.disabled.set(true);
|
|
1414
|
+
this.finishLoadingIfStarted();
|
|
1415
|
+
});
|
|
1416
|
+
this.gridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
1417
|
+
this.disabled.set(false);
|
|
1418
|
+
this.finishLoadingIfStarted();
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
//#endregion
|
|
1422
|
+
//#region Event handlers
|
|
1423
|
+
onFiltersButtonClicked() {
|
|
1424
|
+
this.modal()?.toggleModal();
|
|
1425
|
+
this.formGroup.form.reset();
|
|
1426
|
+
this.formGroup.form.patchValue(this.filters(), { emitEvent: false });
|
|
1427
|
+
}
|
|
1428
|
+
onFiltersSubmitClicked() {
|
|
1429
|
+
const validateFormFunction = this.validateFormFunction();
|
|
1430
|
+
if (validateFormFunction) {
|
|
1431
|
+
validateFormFunction();
|
|
1432
|
+
}
|
|
1433
|
+
if (this.formGroup.form.valid) {
|
|
1434
|
+
this.startLoading();
|
|
1435
|
+
this.modal()?.toggleModal();
|
|
1436
|
+
const formValue = this.formGroup.form.getRawValue();
|
|
1437
|
+
const formFilters = Object.fromEntries(Object.entries(formValue).filter(([_, v]) => (!!v && v !== '') || v === 0));
|
|
1438
|
+
this.setFilters(formFilters);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
onResetButtonClicked() {
|
|
1442
|
+
this.startLoading();
|
|
1443
|
+
this.formGroup.form.reset();
|
|
1444
|
+
this.filters.set({});
|
|
1445
|
+
this.gridDataset.setFilters();
|
|
1446
|
+
}
|
|
1447
|
+
//#endregion
|
|
1448
|
+
//#region Public methods
|
|
1449
|
+
setFilters(filters) {
|
|
1450
|
+
// Kept whole, because reopening the modal patches this straight back into the form and a
|
|
1451
|
+
// catalog select backed by a search endpoint cannot recover its label from the identifier --
|
|
1452
|
+
// it only resolves a display from a local entries list. Dropping the labels here would leave
|
|
1453
|
+
// the field showing a selection with no text.
|
|
1454
|
+
this.filters.set(filters);
|
|
1455
|
+
if (this.hasFiltersApplied) {
|
|
1456
|
+
this.gridDataset.setFilters(this.withoutDisplayControls(filters));
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
this.gridDataset.setFilters();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
//#endregion
|
|
1463
|
+
//#region Private methods
|
|
1464
|
+
finishLoadingIfStarted() {
|
|
1465
|
+
if (this.button()?.loading()) {
|
|
1466
|
+
this.finishLoading('success');
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Drops the controls that only hold a catalog selection's label.
|
|
1471
|
+
*
|
|
1472
|
+
* They are half of how `lib-catalog-select` works and are created by it, so a screen never asked
|
|
1473
|
+
* for them and a backend has no filter behind them. Sent anyway they are dead weight, and worse:
|
|
1474
|
+
* the day a service does filter by a name, it would receive the formatted label rather than the
|
|
1475
|
+
* stored value and quietly match nothing.
|
|
1476
|
+
*
|
|
1477
|
+
* @param filters The filters as submitted.
|
|
1478
|
+
* @returns The filters a backend should receive.
|
|
1479
|
+
*/
|
|
1480
|
+
withoutDisplayControls(filters) {
|
|
1481
|
+
return Object.fromEntries(Object.entries(filters).filter(([key]) => !DisplayControls.isDisplayControl(this.formGroup.form.get(key))));
|
|
1482
|
+
}
|
|
1483
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1484
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonFiltersComponent, isStandalone: true, selector: "framework-button-filters", inputs: { modalSize: { classPropertyName: "modalSize", publicName: "modalSize", isSignal: true, isRequired: false, transformFunction: null }, modalTitle: { classPropertyName: "modalTitle", publicName: "modalTitle", isSignal: true, isRequired: false, transformFunction: null }, validateFormFunction: { classPropertyName: "validateFormFunction", publicName: "validateFormFunction", isSignal: true, isRequired: false, transformFunction: null } }, providers: [FormService, { provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonFiltersComponent) }], viewQueries: [{ propertyName: "modal", first: true, predicate: ModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<lib-ribbon-button\r\n [color]=\"hasFiltersApplied ? 'text-amber-500' : 'text-primary-500'\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"'fa-filter'\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"'Button-Filters'\"\r\n [loading]=\"loading()\"\r\n (action)=\"onFiltersButtonClicked()\"\r\n>\r\n</lib-ribbon-button>\r\n\r\n@if (hasFiltersApplied) {\r\n <lib-ribbon-button\r\n [color]=\"'text-red-500'\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"'fa-broom'\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"'Button-Filters-Clear'\"\r\n [loading]=\"loading()\"\r\n (action)=\"onResetButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<lib-modal\r\n position=\"top\"\r\n [size]=\"modalSize()\"\r\n [title]=\"modalTitle() | translate\"\r\n [closeButtonText]=\"'Button-Modal-Cancel' | translate\"\r\n>\r\n <ng-container body>\r\n <ng-content></ng-content>\r\n </ng-container>\r\n <div footer>\r\n <button type=\"submit\" class=\"btn blue-500\" (click)=\"onFiltersSubmitClicked()\">\r\n {{ 'Button-Filters-Modal-Confirm' | translate }}\r\n </button>\r\n </div>\r\n</lib-modal>\r\n", styles: [":host{display:grid;grid-auto-columns:auto;grid-auto-flow:column}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "lib-modal", inputs: ["closeButtonText", "dialog", "modalProcessing", "position", "size", "title"], outputs: ["closeButtonTextChange", "modalProcessingChange", "closed"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1485
|
+
}
|
|
1486
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonFiltersComponent, decorators: [{
|
|
1487
|
+
type: Component,
|
|
1488
|
+
args: [{ selector: 'framework-button-filters', imports: [ModalComponent, RibbonButtonComponent, TranslatePipe], providers: [FormService, { provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonFiltersComponent) }], template: "<lib-ribbon-button\r\n [color]=\"hasFiltersApplied ? 'text-amber-500' : 'text-primary-500'\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"'fa-filter'\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"'Button-Filters'\"\r\n [loading]=\"loading()\"\r\n (action)=\"onFiltersButtonClicked()\"\r\n>\r\n</lib-ribbon-button>\r\n\r\n@if (hasFiltersApplied) {\r\n <lib-ribbon-button\r\n [color]=\"'text-red-500'\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"'fa-broom'\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"'Button-Filters-Clear'\"\r\n [loading]=\"loading()\"\r\n (action)=\"onResetButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<lib-modal\r\n position=\"top\"\r\n [size]=\"modalSize()\"\r\n [title]=\"modalTitle() | translate\"\r\n [closeButtonText]=\"'Button-Modal-Cancel' | translate\"\r\n>\r\n <ng-container body>\r\n <ng-content></ng-content>\r\n </ng-container>\r\n <div footer>\r\n <button type=\"submit\" class=\"btn blue-500\" (click)=\"onFiltersSubmitClicked()\">\r\n {{ 'Button-Filters-Modal-Confirm' | translate }}\r\n </button>\r\n </div>\r\n</lib-modal>\r\n", styles: [":host{display:grid;grid-auto-columns:auto;grid-auto-flow:column}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
1489
|
+
}], ctorParameters: () => [], propDecorators: { modal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ModalComponent), { isSignal: true }] }], modalSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalSize", required: false }] }], modalTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalTitle", required: false }] }], validateFormFunction: [{ type: i0.Input, args: [{ isSignal: true, alias: "validateFormFunction", required: false }] }] } });
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* Key used in Angular route `data` to identify the framework tab view type.
|
|
1493
|
+
*
|
|
1494
|
+
* Usage in route config:
|
|
1495
|
+
* ```ts
|
|
1496
|
+
* {
|
|
1497
|
+
* path: ':id',
|
|
1498
|
+
* component: MyDetailsViewComponent,
|
|
1499
|
+
* data: { [FRAMEWORK_VIEW_TYPE]: FrameworkViewType.Details, ... },
|
|
1500
|
+
* }
|
|
1501
|
+
* ```
|
|
1502
|
+
*/
|
|
1503
|
+
const FRAMEWORK_VIEW_TYPE = 'frameworkViewType';
|
|
1504
|
+
/**
|
|
1505
|
+
* Values for the {@link FRAMEWORK_VIEW_TYPE} route data key.
|
|
1506
|
+
*/
|
|
1507
|
+
var FrameworkViewType;
|
|
1508
|
+
(function (FrameworkViewType) {
|
|
1509
|
+
/** A details/edit view with data provider and sub-views (breadcrumbs, save, etc.). */
|
|
1510
|
+
FrameworkViewType["Details"] = "details";
|
|
1511
|
+
/** A list/grid view (no data provider, no sub-views). */
|
|
1512
|
+
FrameworkViewType["List"] = "list";
|
|
1513
|
+
})(FrameworkViewType || (FrameworkViewType = {}));
|
|
1514
|
+
|
|
1515
|
+
class Tab {
|
|
1516
|
+
_title;
|
|
1517
|
+
clones = [];
|
|
1518
|
+
entityBaseUrl;
|
|
1519
|
+
isTitleLoading = true;
|
|
1520
|
+
queryParams;
|
|
1521
|
+
get title() {
|
|
1522
|
+
return this._title;
|
|
1523
|
+
}
|
|
1524
|
+
set title(value) {
|
|
1525
|
+
if (this._title !== value) {
|
|
1526
|
+
this._title = value;
|
|
1527
|
+
if (value) {
|
|
1528
|
+
this.isTitleLoading = false;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
url = '';
|
|
1533
|
+
constructor(init) {
|
|
1534
|
+
Object.assign(this, init);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
class ButtonNewComponent extends BaseButton {
|
|
1539
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1540
|
+
endpoint = input('new', /* @ts-ignore */
|
|
1541
|
+
...(ngDevMode ? [{ debugName: "endpoint" }] : /* istanbul ignore next */ []));
|
|
1542
|
+
parameters = input(/* @ts-ignore */
|
|
1543
|
+
...(ngDevMode ? [undefined, { debugName: "parameters" }] : /* istanbul ignore next */ []));
|
|
1544
|
+
path = input(/* @ts-ignore */
|
|
1545
|
+
...(ngDevMode ? [undefined, { debugName: "path" }] : /* istanbul ignore next */ []));
|
|
1546
|
+
//#endregion
|
|
1547
|
+
//#region Variables
|
|
1548
|
+
dataProviderService = inject(DataProviderService, { optional: true });
|
|
1549
|
+
router = inject(Router);
|
|
1550
|
+
tabService = inject(TabService);
|
|
1551
|
+
//#endregion
|
|
1552
|
+
//#region Properties
|
|
1553
|
+
get hasDataProvider() {
|
|
1554
|
+
return !!this.dataProviderService;
|
|
1555
|
+
}
|
|
1556
|
+
get hasDataProviderEntityID() {
|
|
1557
|
+
return this.dataProviderService?.hasEntityID ?? false;
|
|
1558
|
+
}
|
|
1559
|
+
//#endregion
|
|
1560
|
+
//#region Constructor and Angular life cycle methods
|
|
1561
|
+
//#endregion
|
|
1562
|
+
//#region Event handlers
|
|
1563
|
+
onButtonClicked(optionId) {
|
|
1564
|
+
let option = undefined;
|
|
1565
|
+
if (optionId) {
|
|
1566
|
+
option = this.options().find((o) => o.id === optionId);
|
|
1567
|
+
}
|
|
1568
|
+
let path = this.path();
|
|
1569
|
+
if (option && option.path && option.path.length > 0) {
|
|
1570
|
+
path = option.path ?? optionId;
|
|
1571
|
+
}
|
|
1572
|
+
let queryParameters = this.parameters();
|
|
1573
|
+
if (option && option.parameters && Object.keys(option.parameters).length > 0) {
|
|
1574
|
+
queryParameters = { ...queryParameters, ...option.parameters };
|
|
1575
|
+
}
|
|
1576
|
+
// Try to find the route with ':id' parameters; fall back to the current route.
|
|
1577
|
+
const targetRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
|
|
1578
|
+
let url = targetRoute
|
|
1579
|
+
? RouteHelper.getRouteURL(targetRoute.parent)
|
|
1580
|
+
: RouteHelper.getRouteURL(this.router.routerState.root.snapshot, true);
|
|
1581
|
+
if (path && path.length > 0) {
|
|
1582
|
+
url += `/${path}`;
|
|
1583
|
+
}
|
|
1584
|
+
const entityUrl = `${url}/${this.endpoint()}`;
|
|
1585
|
+
const tab = new Tab({
|
|
1586
|
+
entityBaseUrl: entityUrl,
|
|
1587
|
+
queryParams: queryParameters,
|
|
1588
|
+
url: entityUrl,
|
|
1589
|
+
});
|
|
1590
|
+
this.tabService.navigateCurrentTab(tab);
|
|
1591
|
+
}
|
|
1592
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonNewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1593
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonNewComponent, isStandalone: true, selector: "framework-button-new", inputs: { endpoint: { classPropertyName: "endpoint", publicName: "endpoint", isSignal: true, isRequired: false, transformFunction: null }, parameters: { classPropertyName: "parameters", publicName: "parameters", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonNewComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible() && (!hasDataProvider || hasDataProviderEntityID)) {\r\n <lib-ribbon-button\r\n icon=\"fa-plus\"\r\n label=\"Button-New\"\r\n [disabled]=\"disabled() || !isAccessLoaded()\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
1594
|
+
}
|
|
1595
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonNewComponent, decorators: [{
|
|
1596
|
+
type: Component,
|
|
1597
|
+
args: [{ selector: 'framework-button-new', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonNewComponent) }], template: "@if (isVisible() && (!hasDataProvider || hasDataProviderEntityID)) {\r\n <lib-ribbon-button\r\n icon=\"fa-plus\"\r\n label=\"Button-New\"\r\n [disabled]=\"disabled() || !isAccessLoaded()\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
|
|
1598
|
+
}], propDecorators: { endpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "endpoint", required: false }] }], parameters: [{ type: i0.Input, args: [{ isSignal: true, alias: "parameters", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }] } });
|
|
1599
|
+
|
|
1600
|
+
class ButtonOpenRecordComponent extends BaseButton {
|
|
1601
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1602
|
+
elementPath = input(/* @ts-ignore */
|
|
1603
|
+
...(ngDevMode ? [undefined, { debugName: "elementPath" }] : /* istanbul ignore next */ []));
|
|
1604
|
+
rootPath = input(/* @ts-ignore */
|
|
1605
|
+
...(ngDevMode ? [undefined, { debugName: "rootPath" }] : /* istanbul ignore next */ []));
|
|
1606
|
+
//#endregion
|
|
1607
|
+
//#region Variables
|
|
1608
|
+
dataGridDataset = inject(DataGridDataset);
|
|
1609
|
+
router = inject(Router);
|
|
1610
|
+
// A signal rather than a field: the grid announces a selection change from a subscription, and
|
|
1611
|
+
// this is what enables the button.
|
|
1612
|
+
selectionCount = signal(0, /* @ts-ignore */
|
|
1613
|
+
...(ngDevMode ? [{ debugName: "selectionCount" }] : /* istanbul ignore next */ []));
|
|
1614
|
+
tabService = inject(TabService);
|
|
1615
|
+
//#endregion
|
|
1616
|
+
//#region Properties
|
|
1617
|
+
get isSelectedOneRecord() {
|
|
1618
|
+
return this.selectionCount() === 1;
|
|
1619
|
+
}
|
|
1620
|
+
//#endregion
|
|
1621
|
+
//#region Constructor and Angular life cycle methods
|
|
1622
|
+
ngOnInit() {
|
|
1623
|
+
this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
1624
|
+
this.selectionCount.set(this.dataGridDataset.selectedRowKeys.length);
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
//#endregion
|
|
1628
|
+
//#region Event handlers
|
|
1629
|
+
onButtonClicked() {
|
|
1630
|
+
if (this.disabled() || !this.isAccessLoaded() || !this.isSelectedOneRecord || !this.isVisible()) {
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
let path = this.rootPath() ?? this.router.url;
|
|
1634
|
+
const elementPath = this.elementPath();
|
|
1635
|
+
if (elementPath) {
|
|
1636
|
+
path += `/${elementPath}`;
|
|
1637
|
+
}
|
|
1638
|
+
const selectedKey = this.dataGridDataset.selectedRowKeys[0];
|
|
1639
|
+
const selectedID = this.dataGridDataset.getRowID(selectedKey);
|
|
1640
|
+
path += `/${selectedID}`;
|
|
1641
|
+
this.tabService.navigateCurrentTab(new Tab({ entityBaseUrl: path, url: path }));
|
|
1642
|
+
}
|
|
1643
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonOpenRecordComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1644
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonOpenRecordComponent, isStandalone: true, selector: "framework-button-open-record", inputs: { elementPath: { classPropertyName: "elementPath", publicName: "elementPath", isSignal: true, isRequired: false, transformFunction: null }, rootPath: { classPropertyName: "rootPath", publicName: "rootPath", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonOpenRecordComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n icon=\"fa-eye\"\r\n label=\"Button-Open-Record\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || !isSelectedOneRecord\"\r\n [iconSize]=\"iconSize()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
1645
|
+
}
|
|
1646
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonOpenRecordComponent, decorators: [{
|
|
1647
|
+
type: Component,
|
|
1648
|
+
args: [{ selector: 'framework-button-open-record', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonOpenRecordComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n icon=\"fa-eye\"\r\n label=\"Button-Open-Record\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || !isSelectedOneRecord\"\r\n [iconSize]=\"iconSize()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
|
|
1649
|
+
}], propDecorators: { elementPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementPath", required: false }] }], rootPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootPath", required: false }] }] } });
|
|
1650
|
+
|
|
1651
|
+
class ButtonRefreshComponent extends BaseButton {
|
|
1652
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1653
|
+
//#endregion
|
|
1654
|
+
//#region Variables
|
|
1655
|
+
dataGridDataset = inject(DataGridDataset, { optional: true });
|
|
1656
|
+
//#endregion
|
|
1657
|
+
//#region Properties
|
|
1658
|
+
//#endregion
|
|
1659
|
+
//#region Constructor and Angular life cycle methods
|
|
1660
|
+
constructor() {
|
|
1661
|
+
super();
|
|
1662
|
+
this.disabled.set(false);
|
|
1663
|
+
}
|
|
1664
|
+
ngOnInit() {
|
|
1665
|
+
this.dataGridDataset?.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
1666
|
+
this.loading.set(false);
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
//#endregion
|
|
1670
|
+
//#region Event handlers
|
|
1671
|
+
onButtonClicked() {
|
|
1672
|
+
this.loading.set(true);
|
|
1673
|
+
this.dataGridDataset?.refresh();
|
|
1674
|
+
}
|
|
1675
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonRefreshComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1676
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ButtonRefreshComponent, isStandalone: true, selector: "framework-button-refresh", providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonRefreshComponent) }], usesInheritance: true, ngImport: i0, template: "<lib-ribbon-button\r\n label=\"Button-Refresh\"\r\n icon=\"fa-sync-alt\"\r\n [disabled]=\"disabled()\"\r\n [loading]=\"loading()\"\r\n [iconSize]=\"iconSize()\"\r\n (action)=\"onButtonClicked()\"\r\n></lib-ribbon-button>\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
1677
|
+
}
|
|
1678
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonRefreshComponent, decorators: [{
|
|
1679
|
+
type: Component,
|
|
1680
|
+
args: [{ selector: 'framework-button-refresh', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonRefreshComponent) }], template: "<lib-ribbon-button\r\n label=\"Button-Refresh\"\r\n icon=\"fa-sync-alt\"\r\n [disabled]=\"disabled()\"\r\n [loading]=\"loading()\"\r\n [iconSize]=\"iconSize()\"\r\n (action)=\"onButtonClicked()\"\r\n></lib-ribbon-button>\r\n" }]
|
|
1681
|
+
}], ctorParameters: () => [] });
|
|
1682
|
+
|
|
1683
|
+
class ButtonSaveComponent extends BaseButton {
|
|
1684
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1685
|
+
errorModal = viewChild(ErrorModalComponent, /* @ts-ignore */
|
|
1686
|
+
...(ngDevMode ? [{ debugName: "errorModal" }] : /* istanbul ignore next */ []));
|
|
1687
|
+
defaultOption = input(0, /* @ts-ignore */
|
|
1688
|
+
...(ngDevMode ? [{ debugName: "defaultOption" }] : /* istanbul ignore next */ []));
|
|
1689
|
+
//#endregion
|
|
1690
|
+
//#region Variables
|
|
1691
|
+
dataProviderService = inject(DataProviderService);
|
|
1692
|
+
formService = inject(FormService);
|
|
1693
|
+
router = inject(Router);
|
|
1694
|
+
tabService = inject(TabService);
|
|
1695
|
+
//#endregion
|
|
1696
|
+
//#region Properties
|
|
1697
|
+
get isFormEditMode() {
|
|
1698
|
+
return this.formService.isEditMode;
|
|
1699
|
+
}
|
|
1700
|
+
get isFormLoading() {
|
|
1701
|
+
return this.formService.loading;
|
|
1702
|
+
}
|
|
1703
|
+
//#endregion
|
|
1704
|
+
//#region Constructor and Angular life cycle methods
|
|
1705
|
+
constructor() {
|
|
1706
|
+
super();
|
|
1707
|
+
this.options.set([
|
|
1708
|
+
{ id: 'save', label: 'Button-Save', icon: 'fa-save' },
|
|
1709
|
+
{ id: 'save-and-close', label: 'Button-Save-And-Close', icon: 'fa-save' },
|
|
1710
|
+
{ id: 'save-and-new', label: 'Button-Save-And-New', icon: 'fa-save' },
|
|
1711
|
+
]);
|
|
1712
|
+
}
|
|
1713
|
+
//#endregion
|
|
1714
|
+
//#region Event handlers
|
|
1715
|
+
onButtonClicked(option) {
|
|
1716
|
+
this.startLoading();
|
|
1717
|
+
this.formService.markAllAsTouched();
|
|
1718
|
+
if (!this.formService.isValid) {
|
|
1719
|
+
this.finishLoading('warning');
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
this.saveModel()
|
|
1723
|
+
.pipe(take(1))
|
|
1724
|
+
.subscribe({
|
|
1725
|
+
next: (model) => {
|
|
1726
|
+
this.finishLoading('success');
|
|
1727
|
+
this.formService.enableForm();
|
|
1728
|
+
this.formService.model = model;
|
|
1729
|
+
this.formService.cancelEdit();
|
|
1730
|
+
switch (option) {
|
|
1731
|
+
case 'save-and-close':
|
|
1732
|
+
this.tabService.navigateBackOrCloseActiveTab();
|
|
1733
|
+
break;
|
|
1734
|
+
case 'save-and-new': {
|
|
1735
|
+
this.dataProviderService.updateModel(model);
|
|
1736
|
+
const detailsRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
|
|
1737
|
+
const savedEntityUrl = `${RouteHelper.getRouteURL(detailsRoute.parent)}/${model.id}`;
|
|
1738
|
+
const currentRouteUrl = RouteHelper.getRouteURL(detailsRoute);
|
|
1739
|
+
if (currentRouteUrl !== savedEntityUrl) {
|
|
1740
|
+
// Router URL differs from saved entity (new entity, or regular save
|
|
1741
|
+
// that only used location.replaceState). Redirect to sync the router
|
|
1742
|
+
// to /{id} invisibly, then navigate to /new normally. The real second
|
|
1743
|
+
// navigation detaches and caches the /{id} component so back-navigation
|
|
1744
|
+
// reattaches it instantly without reloading from the server.
|
|
1745
|
+
this.tabService.redirectCurrentTab(savedEntityUrl);
|
|
1746
|
+
this.tabService.setRouteRedirect(currentRouteUrl, savedEntityUrl);
|
|
1747
|
+
this.router.navigateByUrl(savedEntityUrl).then(() => {
|
|
1748
|
+
this.navigateToNew();
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
else {
|
|
1752
|
+
// Router is already at /{id}. Navigate to /new directly.
|
|
1753
|
+
// The /{id} component is cached by the reuse strategy.
|
|
1754
|
+
this.navigateToNew();
|
|
1755
|
+
}
|
|
1756
|
+
break;
|
|
1757
|
+
}
|
|
1758
|
+
default: {
|
|
1759
|
+
this.dataProviderService.updateModel(model);
|
|
1760
|
+
const targetRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
|
|
1761
|
+
const url = `${RouteHelper.getRouteURL(targetRoute.parent)}/${model.id}`;
|
|
1762
|
+
if (!this.tabService.isUrlActive(url)) {
|
|
1763
|
+
const currentRoute = RouteHelper.getRouteURL(targetRoute);
|
|
1764
|
+
this.tabService.redirectCurrentTab(url);
|
|
1765
|
+
// Sync the Angular router to /{id} invisibly. Without this, the router
|
|
1766
|
+
// stays at the old URL (e.g., /new) and subsequent navigations to that
|
|
1767
|
+
// URL become no-ops (the New button would stop working after Save).
|
|
1768
|
+
if (currentRoute !== url) {
|
|
1769
|
+
this.tabService.setRouteRedirect(currentRoute, url);
|
|
1770
|
+
this.router.navigateByUrl(url);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
break;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
},
|
|
1777
|
+
error: (e) => {
|
|
1778
|
+
this.finishLoading('warning');
|
|
1779
|
+
this.formService.enableForm();
|
|
1780
|
+
if (e.status === 400) {
|
|
1781
|
+
this.formService.setValidationErrorsFromHttpResponse(e);
|
|
1782
|
+
}
|
|
1783
|
+
else {
|
|
1784
|
+
let errorMessage = 'Modal-Failed-DefaultMessage';
|
|
1785
|
+
if (typeof e.error === 'string') {
|
|
1786
|
+
errorMessage = e.error;
|
|
1787
|
+
}
|
|
1788
|
+
this.errorModal()?.showModal(errorMessage);
|
|
1789
|
+
}
|
|
1790
|
+
},
|
|
1791
|
+
complete: () => {
|
|
1792
|
+
this.formService.loading = false;
|
|
1793
|
+
},
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
//#endregion
|
|
1797
|
+
//#region Public methods
|
|
1798
|
+
finishLoading(status) {
|
|
1799
|
+
super.finishLoading(status);
|
|
1800
|
+
}
|
|
1801
|
+
//#endregion
|
|
1802
|
+
//#region Private methods
|
|
1803
|
+
navigateToNew() {
|
|
1804
|
+
const targetRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
|
|
1805
|
+
const url = targetRoute
|
|
1806
|
+
? RouteHelper.getRouteURL(targetRoute.parent)
|
|
1807
|
+
: RouteHelper.getRouteURL(this.router.routerState.root.snapshot, true);
|
|
1808
|
+
const tab = new Tab({ entityBaseUrl: `${url}/new`, url: `${url}/new` });
|
|
1809
|
+
this.tabService.navigateCurrentTab(tab);
|
|
1810
|
+
}
|
|
1811
|
+
saveModel() {
|
|
1812
|
+
this.formService.disableForm();
|
|
1813
|
+
const model = this.formService.getModelFromForm();
|
|
1814
|
+
return this.dataProviderService.saveModel(model);
|
|
1815
|
+
}
|
|
1816
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonSaveComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1817
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonSaveComponent, isStandalone: true, selector: "framework-button-save", inputs: { defaultOption: { classPropertyName: "defaultOption", publicName: "defaultOption", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonSaveComponent) }], viewQueries: [{ propertyName: "errorModal", first: true, predicate: ErrorModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Save\"\r\n icon=\"fa-save\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || !isFormEditMode || isFormLoading\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-error-modal></framework-error-modal>\r\n", dependencies: [{ kind: "component", type: ErrorModalComponent, selector: "framework-error-modal", inputs: ["errorMessage", "size", "subtitle", "title"], outputs: ["errorMessageChange", "subtitleChange", "titleChange", "closed"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
1818
|
+
}
|
|
1819
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonSaveComponent, decorators: [{
|
|
1820
|
+
type: Component,
|
|
1821
|
+
args: [{ selector: 'framework-button-save', imports: [ErrorModalComponent, RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonSaveComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Save\"\r\n icon=\"fa-save\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || !isFormEditMode || isFormLoading\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-error-modal></framework-error-modal>\r\n" }]
|
|
1822
|
+
}], ctorParameters: () => [], propDecorators: { errorModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ErrorModalComponent), { isSignal: true }] }], defaultOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOption", required: false }] }] } });
|
|
1823
|
+
|
|
1824
|
+
class ButtonViewsComponent extends BaseButton {
|
|
1825
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1826
|
+
detailsViewRoute = input(null, /* @ts-ignore */
|
|
1827
|
+
...(ngDevMode ? [{ debugName: "detailsViewRoute" }] : /* istanbul ignore next */ []));
|
|
1828
|
+
//#endregion
|
|
1829
|
+
//#region Variables
|
|
1830
|
+
baseUrlPath;
|
|
1831
|
+
dataProviderService = inject(DataProviderService, { optional: true });
|
|
1832
|
+
isInternalNavigation = false;
|
|
1833
|
+
router = inject(Router);
|
|
1834
|
+
// A signal rather than a field: switching views changes which option the dropdown marks as the
|
|
1835
|
+
// current one, and the switch arrives from a router event.
|
|
1836
|
+
selectedViewId = signal(undefined, /* @ts-ignore */
|
|
1837
|
+
...(ngDevMode ? [{ debugName: "selectedViewId" }] : /* istanbul ignore next */ []));
|
|
1838
|
+
tabService = inject(TabService);
|
|
1839
|
+
tabViewService = inject(TabViewService);
|
|
1840
|
+
//#endregion
|
|
1841
|
+
//#region Properties
|
|
1842
|
+
get hasOptions() {
|
|
1843
|
+
return this.options().some((option) => (option.isVisible ?? true) &&
|
|
1844
|
+
(option.allowedActions === undefined ||
|
|
1845
|
+
option.allowedActions.length === 0 ||
|
|
1846
|
+
option.isAccessAllowed === true ||
|
|
1847
|
+
(option.isAccessAllowed === undefined && option.allowedActions?.length > 0)));
|
|
1848
|
+
}
|
|
1849
|
+
get isNewEntity() {
|
|
1850
|
+
return !(this.dataProviderService?.hasEntityID ?? false);
|
|
1851
|
+
}
|
|
1852
|
+
get selectedView() {
|
|
1853
|
+
return this.options().find((option) => option.id === this.selectedViewId());
|
|
1854
|
+
}
|
|
1855
|
+
get defaultViewId() {
|
|
1856
|
+
return this.options()[0].id ?? '';
|
|
1857
|
+
}
|
|
1858
|
+
get selectedViewUrl() {
|
|
1859
|
+
let url = this.baseUrlPath ?? '';
|
|
1860
|
+
const selectedView = this.selectedView;
|
|
1861
|
+
if (selectedView && selectedView.id) {
|
|
1862
|
+
url += `/${selectedView.id}`;
|
|
1863
|
+
}
|
|
1864
|
+
return url;
|
|
1865
|
+
}
|
|
1866
|
+
//#endregion
|
|
1867
|
+
//#region Constructor and Angular life cycle methods
|
|
1868
|
+
ngOnInit() {
|
|
1869
|
+
const detailsViewRoute = this.detailsViewRoute();
|
|
1870
|
+
if (detailsViewRoute) {
|
|
1871
|
+
this.options.set(detailsViewRoute.routeConfig?.children
|
|
1872
|
+
?.filter((route) => !!route.data && route.data['ignoreRoute'] !== true)
|
|
1873
|
+
?.map((route) => {
|
|
1874
|
+
const data = route.data ?? {};
|
|
1875
|
+
const option = {
|
|
1876
|
+
id: route.path ?? '',
|
|
1877
|
+
isDisabled: false,
|
|
1878
|
+
isVisible: true,
|
|
1879
|
+
label: data['title'] ?? '',
|
|
1880
|
+
};
|
|
1881
|
+
const icon = data['icon'];
|
|
1882
|
+
if (icon) {
|
|
1883
|
+
option.icon = icon;
|
|
1884
|
+
}
|
|
1885
|
+
const allowedActions = data['allowedActions'];
|
|
1886
|
+
if (allowedActions && allowedActions.length > 0) {
|
|
1887
|
+
option.allowedActions = allowedActions;
|
|
1888
|
+
}
|
|
1889
|
+
return option;
|
|
1890
|
+
}) ?? []);
|
|
1891
|
+
const url = RouteHelper.getRouteURL(detailsViewRoute);
|
|
1892
|
+
this.baseUrlPath = url;
|
|
1893
|
+
const option = detailsViewRoute.children[0]?.url?.map((segment) => segment.path)?.join('/') ?? '';
|
|
1894
|
+
if (option === this.defaultViewId) {
|
|
1895
|
+
// We just need to check if the current option is the default, otherwise the router.events will handle the option selection.
|
|
1896
|
+
this.changeSelectedView(this.defaultViewId);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
this.router.events
|
|
1900
|
+
.pipe(filter((event) => event instanceof NavigationEnd), takeUntil(this.destroy$))
|
|
1901
|
+
.subscribe((event) => {
|
|
1902
|
+
if (this.detailsViewRoute()) {
|
|
1903
|
+
// Use the current router state to get the up-to-date URL, since the stored
|
|
1904
|
+
// detailsViewRoute snapshot is immutable and becomes stale after save redirects
|
|
1905
|
+
// (e.g., /new → /:id).
|
|
1906
|
+
const currentRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
|
|
1907
|
+
// Every details tab keeps this subscription alive, including the ones the reuse
|
|
1908
|
+
// strategy has detached, and the lookup above reads the *router*, which names
|
|
1909
|
+
// whichever tab the navigation went to. Without this guard a detached tab adopted
|
|
1910
|
+
// the base path of the tab being opened and then switched itself to that tab's view,
|
|
1911
|
+
// tearing its own ribbon down and rebuilding it -- which is how an active filter lost
|
|
1912
|
+
// its Clear filters button while the grid stayed filtered. Comparing the route
|
|
1913
|
+
// definition rather than the URL keeps the one navigation that legitimately changes
|
|
1914
|
+
// this tab's path: the redirect from /new to /:id after a save.
|
|
1915
|
+
if (currentRoute && currentRoute.routeConfig === this.detailsViewRoute().routeConfig) {
|
|
1916
|
+
const url = RouteHelper.getRouteURL(currentRoute);
|
|
1917
|
+
this.baseUrlPath = url;
|
|
1918
|
+
const activeViewId = this.getActiveOption(event.url);
|
|
1919
|
+
if (!this.isInternalNavigation) {
|
|
1920
|
+
if (this.selectedViewId() !== activeViewId) {
|
|
1921
|
+
this.changeSelectedView(activeViewId);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
else {
|
|
1925
|
+
this.isInternalNavigation = false;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
//#endregion
|
|
1932
|
+
//#region Event handlers
|
|
1933
|
+
onViewClicked(selectedOption) {
|
|
1934
|
+
this.changeSelectedView(selectedOption);
|
|
1935
|
+
this.isInternalNavigation = true;
|
|
1936
|
+
if (selectedOption === this.defaultViewId) {
|
|
1937
|
+
// Default view: remove sub-view entry, navigate back to detail view
|
|
1938
|
+
this.tabService.replaceCurrentTabSubView(this.baseUrlPath);
|
|
1939
|
+
}
|
|
1940
|
+
else {
|
|
1941
|
+
const tab = new Tab({
|
|
1942
|
+
title: this.selectedView?.label,
|
|
1943
|
+
url: this.selectedViewUrl,
|
|
1944
|
+
});
|
|
1945
|
+
this.tabService.replaceCurrentTabSubView(this.baseUrlPath, tab);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
//#endregion
|
|
1949
|
+
//#region Public methods
|
|
1950
|
+
//#endregion
|
|
1951
|
+
//#region Private methods
|
|
1952
|
+
/**
|
|
1953
|
+
* Marks the chosen view as the current one, which is how the dropdown greys its own entry out.
|
|
1954
|
+
*
|
|
1955
|
+
* The options are rebuilt rather than edited in place: the ribbon button takes the list as an
|
|
1956
|
+
* input, so a field changed inside it is a change nothing can see.
|
|
1957
|
+
*/
|
|
1958
|
+
changeSelectedView(viewId) {
|
|
1959
|
+
this.selectedViewId.set(viewId);
|
|
1960
|
+
this.tabViewService.setActiveView(viewId ?? '');
|
|
1961
|
+
this.options.update((options) => options.map((option) => option.isDisabled === (option.id === viewId) ? option : { ...option, isDisabled: option.id === viewId }));
|
|
1962
|
+
}
|
|
1963
|
+
getActiveOption(url) {
|
|
1964
|
+
const option = url.replace(this.baseUrlPath ?? '', '').substring(1);
|
|
1965
|
+
if (this.options().some((o) => o.id === option)) {
|
|
1966
|
+
return option;
|
|
1967
|
+
}
|
|
1968
|
+
return this.defaultViewId;
|
|
1969
|
+
}
|
|
1970
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonViewsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1971
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonViewsComponent, isStandalone: true, selector: "framework-button-views", inputs: { detailsViewRoute: { classPropertyName: "detailsViewRoute", publicName: "detailsViewRoute", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonViewsComponent) }], usesInheritance: true, ngImport: i0, template: "@if (hasOptions) {\r\n <lib-ribbon-button\r\n icon=\"fa-eye\"\r\n label=\"Button-Views\"\r\n [disabled]=\"!isAccessLoaded() || isNewEntity\"\r\n [options]=\"options()\"\r\n (action)=\"onViewClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
1972
|
+
}
|
|
1973
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonViewsComponent, decorators: [{
|
|
1974
|
+
type: Component,
|
|
1975
|
+
args: [{ selector: 'framework-button-views', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonViewsComponent) }], template: "@if (hasOptions) {\r\n <lib-ribbon-button\r\n icon=\"fa-eye\"\r\n label=\"Button-Views\"\r\n [disabled]=\"!isAccessLoaded() || isNewEntity\"\r\n [options]=\"options()\"\r\n (action)=\"onViewClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
|
|
1976
|
+
}], propDecorators: { detailsViewRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailsViewRoute", required: false }] }] } });
|
|
1977
|
+
|
|
1978
|
+
class ButtonComponent extends BaseButton {
|
|
1979
|
+
//#region ViewChilds, Inputs, Outputs
|
|
1980
|
+
color = input('text-primary-500', /* @ts-ignore */
|
|
1981
|
+
...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
1982
|
+
icon = input(/* @ts-ignore */
|
|
1983
|
+
...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
1984
|
+
label = input('', /* @ts-ignore */
|
|
1985
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1986
|
+
action = output();
|
|
1987
|
+
//#endregion
|
|
1988
|
+
//#region Variables
|
|
1989
|
+
//#endregion
|
|
1990
|
+
//#region Properties
|
|
1991
|
+
//#endregion
|
|
1992
|
+
//#region Constructor and Angular life cycle methods
|
|
1993
|
+
//#endregion
|
|
1994
|
+
//#region Event handlers
|
|
1995
|
+
onButtonClicked() {
|
|
1996
|
+
if (this.disabled() || !this.isAccessLoaded() || !this.isVisible()) {
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
this.action.emit();
|
|
2000
|
+
}
|
|
2001
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2002
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonComponent, isStandalone: true, selector: "framework-button", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [color]=\"color()\"\r\n [disabled]=\"disabled() || !isAccessLoaded()\"\r\n [icon]=\"icon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
2003
|
+
}
|
|
2004
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
2005
|
+
type: Component,
|
|
2006
|
+
args: [{ selector: 'framework-button', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [color]=\"color()\"\r\n [disabled]=\"disabled() || !isAccessLoaded()\"\r\n [icon]=\"icon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
|
|
2007
|
+
}], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], action: [{ type: i0.Output, args: ["action"] }] } });
|
|
2008
|
+
|
|
2009
|
+
/**
|
|
2010
|
+
* @deprecated Use standalone {@link ButtonEditComponent} instead.
|
|
2011
|
+
* Migrate by replacing this legacy component with the standalone equivalent
|
|
2012
|
+
* and using inject() for dependency injection.
|
|
2013
|
+
*/
|
|
2014
|
+
class ButtonEditLegacyComponent extends BaseButton {
|
|
2015
|
+
//#region ViewChilds, Inputs, Outputs
|
|
2016
|
+
cancelColor = input('text-red-500', /* @ts-ignore */
|
|
2017
|
+
...(ngDevMode ? [{ debugName: "cancelColor" }] : /* istanbul ignore next */ []));
|
|
2018
|
+
cancelIcon = input('fa-ban', /* @ts-ignore */
|
|
2019
|
+
...(ngDevMode ? [{ debugName: "cancelIcon" }] : /* istanbul ignore next */ []));
|
|
2020
|
+
cancelLabel = input('Button-Cancel-Edit', /* @ts-ignore */
|
|
2021
|
+
...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
2022
|
+
editIcon = input('fa-edit', /* @ts-ignore */
|
|
2023
|
+
...(ngDevMode ? [{ debugName: "editIcon" }] : /* istanbul ignore next */ []));
|
|
2024
|
+
editLabel = input('Button-Edit', /* @ts-ignore */
|
|
2025
|
+
...(ngDevMode ? [{ debugName: "editLabel" }] : /* istanbul ignore next */ []));
|
|
2026
|
+
form = input(/* @ts-ignore */
|
|
2027
|
+
...(ngDevMode ? [undefined, { debugName: "form" }] : /* istanbul ignore next */ []));
|
|
2028
|
+
modal = input(/* @ts-ignore */
|
|
2029
|
+
...(ngDevMode ? [undefined, { debugName: "modal" }] : /* istanbul ignore next */ []));
|
|
2030
|
+
//#endregion
|
|
2031
|
+
//#region Variables
|
|
2032
|
+
//#endregion
|
|
2033
|
+
//#region Properties
|
|
2034
|
+
//#endregion
|
|
2035
|
+
//#region Constructor and Angular life cycle methods
|
|
2036
|
+
//#endregion
|
|
2037
|
+
//#region Event handlers
|
|
2038
|
+
onCancelAction() {
|
|
2039
|
+
this.form()?.cancelEdit();
|
|
2040
|
+
}
|
|
2041
|
+
onEditAction() {
|
|
2042
|
+
const form = this.form();
|
|
2043
|
+
if (form) {
|
|
2044
|
+
form.beginEdit();
|
|
2045
|
+
}
|
|
2046
|
+
else {
|
|
2047
|
+
this.modal()?.toggle();
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonEditLegacyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2051
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonEditLegacyComponent, isStandalone: true, selector: "framework-button-edit-legacy", inputs: { cancelColor: { classPropertyName: "cancelColor", publicName: "cancelColor", isSignal: true, isRequired: false, transformFunction: null }, cancelIcon: { classPropertyName: "cancelIcon", publicName: "cancelIcon", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, editIcon: { classPropertyName: "editIcon", publicName: "editIcon", isSignal: true, isRequired: false, transformFunction: null }, editLabel: { classPropertyName: "editLabel", publicName: "editLabel", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonEditLegacyComponent) }], usesInheritance: true, ngImport: i0, template: "@let legacyForm = form();\r\n\r\n@if (isVisible() && (!legacyForm || legacyForm.viewMode !== 'edit')) {\r\n <lib-ribbon-button\r\n [disabled]=\"disabled() || (!!legacyForm && legacyForm.loading)\"\r\n [icon]=\"editIcon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"editLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onEditAction()\"\r\n ></lib-ribbon-button>\r\n}\r\n@if (isVisible() && legacyForm && legacyForm.viewMode === 'edit' && !legacyForm.isNew) {\r\n <lib-ribbon-button\r\n [color]=\"cancelColor()\"\r\n [disabled]=\"disabled() || legacyForm.loading\"\r\n [icon]=\"cancelIcon()\"\r\n [label]=\"cancelLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onCancelAction()\"\r\n ></lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
2052
|
+
}
|
|
2053
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonEditLegacyComponent, decorators: [{
|
|
2054
|
+
type: Component,
|
|
2055
|
+
args: [{ selector: 'framework-button-edit-legacy', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonEditLegacyComponent) }], template: "@let legacyForm = form();\r\n\r\n@if (isVisible() && (!legacyForm || legacyForm.viewMode !== 'edit')) {\r\n <lib-ribbon-button\r\n [disabled]=\"disabled() || (!!legacyForm && legacyForm.loading)\"\r\n [icon]=\"editIcon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"editLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onEditAction()\"\r\n ></lib-ribbon-button>\r\n}\r\n@if (isVisible() && legacyForm && legacyForm.viewMode === 'edit' && !legacyForm.isNew) {\r\n <lib-ribbon-button\r\n [color]=\"cancelColor()\"\r\n [disabled]=\"disabled() || legacyForm.loading\"\r\n [icon]=\"cancelIcon()\"\r\n [label]=\"cancelLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onCancelAction()\"\r\n ></lib-ribbon-button>\r\n}\r\n" }]
|
|
2056
|
+
}], propDecorators: { cancelColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelColor", required: false }] }], cancelIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelIcon", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], editIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "editIcon", required: false }] }], editLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "editLabel", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }] } });
|
|
2057
|
+
|
|
2058
|
+
/**
|
|
2059
|
+
* @deprecated Use standalone {@link ButtonNewComponent} instead.
|
|
2060
|
+
* Migrate by replacing this legacy component with the standalone equivalent
|
|
2061
|
+
* and using inject() for dependency injection.
|
|
2062
|
+
*/
|
|
2063
|
+
class ButtonNewLegacyComponent extends BaseButton {
|
|
2064
|
+
//#region ViewChilds, Inputs, Outputs
|
|
2065
|
+
endpoint = input('new', /* @ts-ignore */
|
|
2066
|
+
...(ngDevMode ? [{ debugName: "endpoint" }] : /* istanbul ignore next */ []));
|
|
2067
|
+
path = input('', /* @ts-ignore */
|
|
2068
|
+
...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
|
|
2069
|
+
tabView = input(/* @ts-ignore */
|
|
2070
|
+
...(ngDevMode ? [undefined, { debugName: "tabView" }] : /* istanbul ignore next */ []));
|
|
2071
|
+
//#endregion
|
|
2072
|
+
//#region Variables
|
|
2073
|
+
tabService = inject(TabService);
|
|
2074
|
+
//#endregion
|
|
2075
|
+
//#region Properties
|
|
2076
|
+
//#endregion
|
|
2077
|
+
//#region Constructor and Angular life cycle methods
|
|
2078
|
+
//#endregion
|
|
2079
|
+
//#region Event handlers
|
|
2080
|
+
//#endregion
|
|
2081
|
+
//#region Public methods
|
|
2082
|
+
clicked() {
|
|
2083
|
+
this.tabService.navigateTo(new Tab({ url: `${this.path()}/${this.endpoint()}` }));
|
|
2084
|
+
// this.tabService.navigateTab(this.tabView, `${this.path}/${this.endpoint}`);
|
|
2085
|
+
}
|
|
2086
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonNewLegacyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2087
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonNewLegacyComponent, isStandalone: true, selector: "framework-button-new-legacy", inputs: { endpoint: { classPropertyName: "endpoint", publicName: "endpoint", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, tabView: { classPropertyName: "tabView", publicName: "tabView", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonNewLegacyComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [label]=\"'Button-New'\"\r\n [icon]=\"'fa-plus'\"\r\n [disabled]=\"disabled()\"\r\n [loading]=\"loading()\"\r\n (action)=\"clicked()\"\r\n ></lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
2088
|
+
}
|
|
2089
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonNewLegacyComponent, decorators: [{
|
|
2090
|
+
type: Component,
|
|
2091
|
+
args: [{ selector: 'framework-button-new-legacy', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonNewLegacyComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [label]=\"'Button-New'\"\r\n [icon]=\"'fa-plus'\"\r\n [disabled]=\"disabled()\"\r\n [loading]=\"loading()\"\r\n (action)=\"clicked()\"\r\n ></lib-ribbon-button>\r\n}\r\n" }]
|
|
2092
|
+
}], propDecorators: { endpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "endpoint", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }], tabView: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabView", required: false }] }] } });
|
|
2093
|
+
|
|
2094
|
+
/**
|
|
2095
|
+
* @deprecated Use standalone {@link ButtonOpenRecordComponent} instead.
|
|
2096
|
+
* Migrate by replacing this legacy component with the standalone equivalent
|
|
2097
|
+
* and using inject() for dependency injection.
|
|
2098
|
+
*/
|
|
2099
|
+
class ButtonOpenRecordLegacyComponent extends BaseButton {
|
|
2100
|
+
//#region ViewChilds, Inputs, Outputs
|
|
2101
|
+
endpoint = input('new', /* @ts-ignore */
|
|
2102
|
+
...(ngDevMode ? [{ debugName: "endpoint" }] : /* istanbul ignore next */ []));
|
|
2103
|
+
path = input('', /* @ts-ignore */
|
|
2104
|
+
...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
|
|
2105
|
+
tabView = input(/* @ts-ignore */
|
|
2106
|
+
...(ngDevMode ? [undefined, { debugName: "tabView" }] : /* istanbul ignore next */ []));
|
|
2107
|
+
//#endregion
|
|
2108
|
+
//#region Variables
|
|
2109
|
+
dataGridDataset = inject(DataGridDataset);
|
|
2110
|
+
// A signal rather than a field: the grid announces a selection change from a subscription.
|
|
2111
|
+
selectionCount = signal(0, /* @ts-ignore */
|
|
2112
|
+
...(ngDevMode ? [{ debugName: "selectionCount" }] : /* istanbul ignore next */ []));
|
|
2113
|
+
tabService = inject(TabService);
|
|
2114
|
+
//#endregion
|
|
2115
|
+
//#region Properties
|
|
2116
|
+
//#endregion
|
|
2117
|
+
//#region Constructor and Angular life cycle methods
|
|
2118
|
+
ngOnInit() {
|
|
2119
|
+
this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
2120
|
+
this.selectionCount.set(this.dataGridDataset.selectedRowKeys.length);
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
//#endregion
|
|
2124
|
+
//#region Event handlers
|
|
2125
|
+
//#endregion
|
|
2126
|
+
//#region Public methods
|
|
2127
|
+
clicked() {
|
|
2128
|
+
if (this.dataGridDataset.hasSelectedRows && this.selectionCount() === 1) {
|
|
2129
|
+
const selectedKey = this.dataGridDataset.selectedRowKeys[0];
|
|
2130
|
+
const selectedID = this.dataGridDataset.getRowID(selectedKey);
|
|
2131
|
+
this.tabService.navigateTo(new Tab({ url: `${this.path()}/${selectedID}` }));
|
|
2132
|
+
// this.tabService.navigateTab(this.tabView, `${this.path}/${this.dataGridDataset.selectedRows[0]}`);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonOpenRecordLegacyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2136
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonOpenRecordLegacyComponent, isStandalone: true, selector: "framework-button-open-record-legacy", inputs: { endpoint: { classPropertyName: "endpoint", publicName: "endpoint", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, tabView: { classPropertyName: "tabView", publicName: "tabView", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonOpenRecordLegacyComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [label]=\"'Button-Open-Record'\"\r\n [icon]=\"'fa-eye'\"\r\n [disabled]=\"disabled() || selectionCount() !== 1\"\r\n [loading]=\"loading()\"\r\n (action)=\"clicked()\"\r\n ></lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
2137
|
+
}
|
|
2138
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonOpenRecordLegacyComponent, decorators: [{
|
|
2139
|
+
type: Component,
|
|
2140
|
+
args: [{ selector: 'framework-button-open-record-legacy', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonOpenRecordLegacyComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [label]=\"'Button-Open-Record'\"\r\n [icon]=\"'fa-eye'\"\r\n [disabled]=\"disabled() || selectionCount() !== 1\"\r\n [loading]=\"loading()\"\r\n (action)=\"clicked()\"\r\n ></lib-ribbon-button>\r\n}\r\n" }]
|
|
2141
|
+
}], propDecorators: { endpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "endpoint", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }], tabView: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabView", required: false }] }] } });
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* @deprecated Use standalone {@link ButtonSaveComponent} instead.
|
|
2145
|
+
* Migrate by replacing this legacy component with the standalone equivalent
|
|
2146
|
+
* and using inject() for dependency injection.
|
|
2147
|
+
*/
|
|
2148
|
+
class ButtonSaveLegacyComponent extends BaseButton {
|
|
2149
|
+
//#region ViewChilds, Inputs, Outputs
|
|
2150
|
+
errorModal = viewChild(ErrorModalComponent, /* @ts-ignore */
|
|
2151
|
+
...(ngDevMode ? [{ debugName: "errorModal" }] : /* istanbul ignore next */ []));
|
|
2152
|
+
defaultOption = input(0, /* @ts-ignore */
|
|
2153
|
+
...(ngDevMode ? [{ debugName: "defaultOption" }] : /* istanbul ignore next */ []));
|
|
2154
|
+
form = input(/* @ts-ignore */
|
|
2155
|
+
...(ngDevMode ? [undefined, { debugName: "form" }] : /* istanbul ignore next */ []));
|
|
2156
|
+
tabView = input(/* @ts-ignore */
|
|
2157
|
+
...(ngDevMode ? [undefined, { debugName: "tabView" }] : /* istanbul ignore next */ []));
|
|
2158
|
+
//#endregion
|
|
2159
|
+
//#region Variables
|
|
2160
|
+
formService = inject(FormService);
|
|
2161
|
+
tabService = inject(TabService);
|
|
2162
|
+
//#endregion
|
|
2163
|
+
//#region Properties
|
|
2164
|
+
//#endregion
|
|
2165
|
+
//#region Constructor and Angular life cycle methods
|
|
2166
|
+
constructor() {
|
|
2167
|
+
super();
|
|
2168
|
+
this.options.set([
|
|
2169
|
+
{ id: 'save', label: 'Button-Save', icon: 'fa-save' },
|
|
2170
|
+
{ id: 'save-and-close', label: 'Button-Save-And-Close', icon: 'fa-save' },
|
|
2171
|
+
{ id: 'save-and-new', label: 'Button-Save-And-New', icon: 'fa-save' },
|
|
2172
|
+
]);
|
|
2173
|
+
}
|
|
2174
|
+
//#endregion
|
|
2175
|
+
//#region Event handlers
|
|
2176
|
+
onButtonClick(option) {
|
|
2177
|
+
const form = this.form();
|
|
2178
|
+
if (!form) {
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
this.startLoading();
|
|
2182
|
+
this.setTabViewLoading(true);
|
|
2183
|
+
form
|
|
2184
|
+
.save()
|
|
2185
|
+
.pipe(take(1))
|
|
2186
|
+
.subscribe({
|
|
2187
|
+
next: (model) => {
|
|
2188
|
+
this.finishLoading('success');
|
|
2189
|
+
this.formService.model = model;
|
|
2190
|
+
form.cancelEdit();
|
|
2191
|
+
switch (option) {
|
|
2192
|
+
case 'save-and-close':
|
|
2193
|
+
this.tabService.closeActiveTab();
|
|
2194
|
+
// this.tabService.closeTab(this.tabView);
|
|
2195
|
+
break;
|
|
2196
|
+
case 'save-and-new':
|
|
2197
|
+
this.tabView()?.buttonNew?.clicked();
|
|
2198
|
+
break;
|
|
2199
|
+
}
|
|
2200
|
+
},
|
|
2201
|
+
error: (e) => {
|
|
2202
|
+
this.finishLoading('warning');
|
|
2203
|
+
form.beginEdit(false);
|
|
2204
|
+
if (e.status === 400) {
|
|
2205
|
+
BackendFormValidationHelper.validateAllFormFields(e, form.dataForm);
|
|
2206
|
+
}
|
|
2207
|
+
else {
|
|
2208
|
+
let errorMessage = 'Modal-Failed-DefaultMessage';
|
|
2209
|
+
if (typeof e.error === 'string') {
|
|
2210
|
+
errorMessage = e.error;
|
|
2211
|
+
}
|
|
2212
|
+
this.errorModal()?.showModal(errorMessage);
|
|
2213
|
+
}
|
|
2214
|
+
},
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
//#endregion
|
|
2218
|
+
//#region Public methods
|
|
2219
|
+
finishLoading(status) {
|
|
2220
|
+
this.setTabViewLoading(false);
|
|
2221
|
+
super.finishLoading(status);
|
|
2222
|
+
}
|
|
2223
|
+
//#endregion
|
|
2224
|
+
//#region Private methods
|
|
2225
|
+
setTabViewLoading(isLoading) {
|
|
2226
|
+
const tabView = this.tabView();
|
|
2227
|
+
if (tabView) {
|
|
2228
|
+
tabView.loading = isLoading;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonSaveLegacyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2232
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonSaveLegacyComponent, isStandalone: true, selector: "framework-button-save-legacy", inputs: { defaultOption: { classPropertyName: "defaultOption", publicName: "defaultOption", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, tabView: { classPropertyName: "tabView", publicName: "tabView", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonSaveLegacyComponent) }], viewQueries: [{ propertyName: "errorModal", first: true, predicate: ErrorModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@let legacyForm = form();\r\n\r\n@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Save\"\r\n icon=\"fa-save\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"disabled() || legacyForm?.viewMode !== 'edit' || (legacyForm?.loading ?? false)\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClick($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-error-modal></framework-error-modal>\r\n", dependencies: [{ kind: "component", type: ErrorModalComponent, selector: "framework-error-modal", inputs: ["errorMessage", "size", "subtitle", "title"], outputs: ["errorMessageChange", "subtitleChange", "titleChange", "closed"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
2233
|
+
}
|
|
2234
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonSaveLegacyComponent, decorators: [{
|
|
2235
|
+
type: Component,
|
|
2236
|
+
args: [{ selector: 'framework-button-save-legacy', imports: [ErrorModalComponent, RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonSaveLegacyComponent) }], template: "@let legacyForm = form();\r\n\r\n@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Save\"\r\n icon=\"fa-save\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"disabled() || legacyForm?.viewMode !== 'edit' || (legacyForm?.loading ?? false)\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClick($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-error-modal></framework-error-modal>\r\n" }]
|
|
2237
|
+
}], ctorParameters: () => [], propDecorators: { errorModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ErrorModalComponent), { isSignal: true }] }], defaultOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOption", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], tabView: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabView", required: false }] }] } });
|
|
2238
|
+
|
|
2239
|
+
/**
|
|
2240
|
+
* @deprecated Use standalone {@link ButtonViewsComponent} instead.
|
|
2241
|
+
* Migrate by replacing this legacy component with the standalone equivalent
|
|
2242
|
+
* and using inject() for dependency injection.
|
|
2243
|
+
*/
|
|
2244
|
+
class ButtonViewsLegacyComponent extends BaseButton {
|
|
2245
|
+
views = input([], /* @ts-ignore */
|
|
2246
|
+
...(ngDevMode ? [{ debugName: "views" }] : /* istanbul ignore next */ []));
|
|
2247
|
+
viewChanged = output();
|
|
2248
|
+
clicked(option) {
|
|
2249
|
+
this.viewChanged.emit(option);
|
|
2250
|
+
}
|
|
2251
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonViewsLegacyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2252
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: ButtonViewsLegacyComponent, isStandalone: true, selector: "framework-button-views-legacy", inputs: { views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { viewChanged: "viewChanged" }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonViewsLegacyComponent) }], usesInheritance: true, ngImport: i0, template: "<lib-ribbon-button\r\n [label]=\"'Button-Views'\"\r\n [icon]=\"'fa-eye'\"\r\n [options]=\"views()\"\r\n (action)=\"clicked($event)\"\r\n></lib-ribbon-button>\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
|
|
2253
|
+
}
|
|
2254
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonViewsLegacyComponent, decorators: [{
|
|
2255
|
+
type: Component,
|
|
2256
|
+
args: [{ selector: 'framework-button-views-legacy', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonViewsLegacyComponent) }], template: "<lib-ribbon-button\r\n [label]=\"'Button-Views'\"\r\n [icon]=\"'fa-eye'\"\r\n [options]=\"views()\"\r\n (action)=\"clicked($event)\"\r\n></lib-ribbon-button>\r\n" }]
|
|
2257
|
+
}], propDecorators: { views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], viewChanged: [{ type: i0.Output, args: ["viewChanged"] }] } });
|
|
2258
|
+
|
|
2259
|
+
/**
|
|
2260
|
+
* @deprecated Use standalone component imports instead of FrameworkButtonsModule.
|
|
2261
|
+
* Import individual button components directly in your component's `imports` array.
|
|
2262
|
+
*/
|
|
2263
|
+
class FrameworkButtonsModule {
|
|
2264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkButtonsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2265
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkButtonsModule, imports: [ButtonDeleteComponent,
|
|
2266
|
+
ButtonEditComponent,
|
|
2267
|
+
ButtonExportComponent,
|
|
2268
|
+
ButtonFiltersComponent,
|
|
2269
|
+
ButtonNewComponent,
|
|
2270
|
+
ButtonOpenRecordComponent,
|
|
2271
|
+
ButtonRefreshComponent,
|
|
2272
|
+
ButtonSaveComponent,
|
|
2273
|
+
ButtonViewsComponent,
|
|
2274
|
+
ButtonNewLegacyComponent,
|
|
2275
|
+
ButtonEditLegacyComponent,
|
|
2276
|
+
ButtonSaveLegacyComponent,
|
|
2277
|
+
ButtonViewsLegacyComponent,
|
|
2278
|
+
ButtonOpenRecordLegacyComponent], exports: [ButtonDeleteComponent,
|
|
2279
|
+
ButtonEditComponent,
|
|
2280
|
+
ButtonExportComponent,
|
|
2281
|
+
ButtonFiltersComponent,
|
|
2282
|
+
ButtonNewComponent,
|
|
2283
|
+
ButtonOpenRecordComponent,
|
|
2284
|
+
ButtonRefreshComponent,
|
|
2285
|
+
ButtonSaveComponent,
|
|
2286
|
+
ButtonViewsComponent,
|
|
2287
|
+
ButtonNewLegacyComponent,
|
|
2288
|
+
ButtonEditLegacyComponent,
|
|
2289
|
+
ButtonSaveLegacyComponent,
|
|
2290
|
+
ButtonViewsLegacyComponent,
|
|
2291
|
+
ButtonOpenRecordLegacyComponent] });
|
|
2292
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkButtonsModule, imports: [ButtonDeleteComponent,
|
|
2293
|
+
ButtonEditComponent,
|
|
2294
|
+
ButtonExportComponent,
|
|
2295
|
+
ButtonFiltersComponent,
|
|
2296
|
+
ButtonNewComponent,
|
|
2297
|
+
ButtonOpenRecordComponent,
|
|
2298
|
+
ButtonRefreshComponent,
|
|
2299
|
+
ButtonSaveComponent,
|
|
2300
|
+
ButtonViewsComponent,
|
|
2301
|
+
ButtonNewLegacyComponent,
|
|
2302
|
+
ButtonEditLegacyComponent,
|
|
2303
|
+
ButtonSaveLegacyComponent,
|
|
2304
|
+
ButtonViewsLegacyComponent,
|
|
2305
|
+
ButtonOpenRecordLegacyComponent] });
|
|
2306
|
+
}
|
|
2307
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkButtonsModule, decorators: [{
|
|
2308
|
+
type: NgModule,
|
|
2309
|
+
args: [{
|
|
2310
|
+
declarations: [],
|
|
2311
|
+
imports: [
|
|
2312
|
+
ButtonDeleteComponent,
|
|
2313
|
+
ButtonEditComponent,
|
|
2314
|
+
ButtonExportComponent,
|
|
2315
|
+
ButtonFiltersComponent,
|
|
2316
|
+
ButtonNewComponent,
|
|
2317
|
+
ButtonOpenRecordComponent,
|
|
2318
|
+
ButtonRefreshComponent,
|
|
2319
|
+
ButtonSaveComponent,
|
|
2320
|
+
ButtonViewsComponent,
|
|
2321
|
+
ButtonNewLegacyComponent,
|
|
2322
|
+
ButtonEditLegacyComponent,
|
|
2323
|
+
ButtonSaveLegacyComponent,
|
|
2324
|
+
ButtonViewsLegacyComponent,
|
|
2325
|
+
ButtonOpenRecordLegacyComponent,
|
|
2326
|
+
],
|
|
2327
|
+
exports: [
|
|
2328
|
+
ButtonDeleteComponent,
|
|
2329
|
+
ButtonEditComponent,
|
|
2330
|
+
ButtonExportComponent,
|
|
2331
|
+
ButtonFiltersComponent,
|
|
2332
|
+
ButtonNewComponent,
|
|
2333
|
+
ButtonOpenRecordComponent,
|
|
2334
|
+
ButtonRefreshComponent,
|
|
2335
|
+
ButtonSaveComponent,
|
|
2336
|
+
ButtonViewsComponent,
|
|
2337
|
+
ButtonNewLegacyComponent,
|
|
2338
|
+
ButtonEditLegacyComponent,
|
|
2339
|
+
ButtonSaveLegacyComponent,
|
|
2340
|
+
ButtonViewsLegacyComponent,
|
|
2341
|
+
ButtonOpenRecordLegacyComponent,
|
|
2342
|
+
],
|
|
2343
|
+
}]
|
|
2344
|
+
}] });
|
|
2345
|
+
|
|
2346
|
+
class DefaultTabViewComponent {
|
|
2347
|
+
//#region ViewChilds, Inputs, Outputs
|
|
2348
|
+
//#endregion
|
|
2349
|
+
//#region Variables
|
|
2350
|
+
destroy$ = new Subject();
|
|
2351
|
+
ribbonTemplate = signal(undefined, /* @ts-ignore */
|
|
2352
|
+
...(ngDevMode ? [{ debugName: "ribbonTemplate" }] : /* istanbul ignore next */ []));
|
|
2353
|
+
hasNamedAView = false;
|
|
2354
|
+
ribbonViewTemplate = {};
|
|
2355
|
+
//#endregion
|
|
2356
|
+
//#region Properties
|
|
2357
|
+
//#endregion
|
|
2358
|
+
//#region Constructor and Angular life cycle methods
|
|
2359
|
+
tabViewService = inject(TabViewService);
|
|
2360
|
+
ngOnDestroy() {
|
|
2361
|
+
this.destroy$.next(true);
|
|
2362
|
+
this.destroy$.complete();
|
|
2363
|
+
}
|
|
2364
|
+
ngOnInit() {
|
|
2365
|
+
this.tabViewService.onUpdateRibbonTemplate
|
|
2366
|
+
.pipe(takeUntil(this.destroy$))
|
|
2367
|
+
.subscribe((template) => {
|
|
2368
|
+
this.ribbonViewTemplate[this.tabViewService.activeView] = template;
|
|
2369
|
+
this.updateRibbonTemplate(template);
|
|
2370
|
+
});
|
|
2371
|
+
this.tabViewService.onViewChanged.pipe(takeUntil(this.destroy$)).subscribe((viewId) => {
|
|
2372
|
+
this.claimUnnamedRibbon(viewId);
|
|
2373
|
+
this.updateRibbonTemplate(this.ribbonViewTemplate[viewId]);
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
//#endregion
|
|
2377
|
+
//#region Event handlers
|
|
2378
|
+
//#endregion
|
|
2379
|
+
//#region Public methods
|
|
2380
|
+
//#endregion
|
|
2381
|
+
//#region Private methods
|
|
2382
|
+
/**
|
|
2383
|
+
* Claims the ribbon published before the active view had a name.
|
|
2384
|
+
*
|
|
2385
|
+
* A child view publishes its ribbon from ngAfterViewInit, and the view is only named afterwards,
|
|
2386
|
+
* so the first template of a tab is always cached under an empty id. For a tab opened straight
|
|
2387
|
+
* at a non-default view that id is never looked up again, the ribbon is emptied, and nothing
|
|
2388
|
+
* refills it -- a tab being re-activated has its child re-attached rather than re-created, so
|
|
2389
|
+
* ngAfterViewInit does not run a second time.
|
|
2390
|
+
*
|
|
2391
|
+
* Only the very first naming may claim it. The empty id is not a spare bucket: it is the default
|
|
2392
|
+
* view's own id, because that view is the one whose URL carries no sub-path. Once the user
|
|
2393
|
+
* starts switching views the unnamed template belongs to the view being *left*, and handing it
|
|
2394
|
+
* to the view being entered showed the previous view's buttons on the new one and left the
|
|
2395
|
+
* previous one with no buttons at all.
|
|
2396
|
+
*
|
|
2397
|
+
* @param viewId The view that has just become active.
|
|
2398
|
+
*/
|
|
2399
|
+
claimUnnamedRibbon(viewId) {
|
|
2400
|
+
const isFirstNaming = !this.hasNamedAView;
|
|
2401
|
+
this.hasNamedAView = true;
|
|
2402
|
+
if (!isFirstNaming ||
|
|
2403
|
+
!viewId ||
|
|
2404
|
+
this.ribbonViewTemplate[viewId] !== undefined ||
|
|
2405
|
+
this.ribbonViewTemplate[''] === undefined) {
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
this.ribbonViewTemplate[viewId] = this.ribbonViewTemplate[''];
|
|
2409
|
+
delete this.ribbonViewTemplate[''];
|
|
2410
|
+
}
|
|
2411
|
+
/**
|
|
2412
|
+
* One signal write, where there used to be a detectChanges()/markForCheck() pair.
|
|
2413
|
+
*
|
|
2414
|
+
* Those existed to render a real template at once while deferring a clear, so switching views
|
|
2415
|
+
* did not tear an already rendered ribbon down for a frame before the incoming view pushed its
|
|
2416
|
+
* own. A signal gets both for free: writes coalesce into the next pass, so a clear followed by
|
|
2417
|
+
* a set moments later is one render, not two. The NG0100 hazard goes with it -- a signal write
|
|
2418
|
+
* after the outlet has been checked schedules another pass instead of throwing.
|
|
2419
|
+
*/
|
|
2420
|
+
updateRibbonTemplate(template) {
|
|
2421
|
+
this.ribbonTemplate.set(template);
|
|
2422
|
+
}
|
|
2423
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: DefaultTabViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2424
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: DefaultTabViewComponent, isStandalone: true, selector: "framework-default-tab-view", providers: [{ provide: TabViewService }], ngImport: i0, template: "<lib-ribbon>\r\n <ng-container *ngTemplateOutlet=\"ribbonTemplate() ?? null\"></ng-container>\r\n</lib-ribbon>\r\n\r\n<router-outlet> </router-outlet>\r\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-grow:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow:hidden}router-outlet{display:none}:host ::ng-deep .framework-view-list{display:flex;flex-grow:1;flex-direction:column;overflow:hidden;min-height:0}:host ::ng-deep .framework-view-list lib-data-grid{flex-grow:1}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: RibbonComponent, selector: "lib-ribbon" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] });
|
|
2425
|
+
}
|
|
2426
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: DefaultTabViewComponent, decorators: [{
|
|
2427
|
+
type: Component,
|
|
2428
|
+
args: [{ selector: 'framework-default-tab-view', imports: [NgTemplateOutlet, RibbonComponent, RouterModule], providers: [{ provide: TabViewService }], template: "<lib-ribbon>\r\n <ng-container *ngTemplateOutlet=\"ribbonTemplate() ?? null\"></ng-container>\r\n</lib-ribbon>\r\n\r\n<router-outlet> </router-outlet>\r\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-grow:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow:hidden}router-outlet{display:none}:host ::ng-deep .framework-view-list{display:flex;flex-grow:1;flex-direction:column;overflow:hidden;min-height:0}:host ::ng-deep .framework-view-list lib-data-grid{flex-grow:1}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
2429
|
+
}] });
|
|
2430
|
+
|
|
2431
|
+
function dataProviderFactory(route) {
|
|
2432
|
+
return route.snapshot.data['dataProvider']();
|
|
2433
|
+
}
|
|
2434
|
+
class DefaultDetailsTabViewComponent extends DefaultTabViewComponent {
|
|
2435
|
+
//#region ViewChilds, Inputs, Outputs
|
|
2436
|
+
errorModal = viewChild(ErrorModalComponent, /* @ts-ignore */
|
|
2437
|
+
...(ngDevMode ? [{ debugName: "errorModal" }] : /* istanbul ignore next */ []));
|
|
2438
|
+
//#endregion
|
|
2439
|
+
//#region Variables
|
|
2440
|
+
// Signals rather than fields: the record's title arrives from the data provider's stream,
|
|
2441
|
+
// and the route it is read from is recomputed in that same subscription. Both are rendered
|
|
2442
|
+
// here -- the container heading and the views button -- with nothing else marking this view.
|
|
2443
|
+
containerIcon = signal(undefined, /* @ts-ignore */
|
|
2444
|
+
...(ngDevMode ? [{ debugName: "containerIcon" }] : /* istanbul ignore next */ []));
|
|
2445
|
+
containerTitle = signal(undefined, /* @ts-ignore */
|
|
2446
|
+
...(ngDevMode ? [{ debugName: "containerTitle" }] : /* istanbul ignore next */ []));
|
|
2447
|
+
detailsViewRoute = signal(null, /* @ts-ignore */
|
|
2448
|
+
...(ngDevMode ? [{ debugName: "detailsViewRoute" }] : /* istanbul ignore next */ []));
|
|
2449
|
+
dataProviderClassName;
|
|
2450
|
+
dataProviderService = inject(DataProviderService, { optional: true });
|
|
2451
|
+
hasEntityID = null;
|
|
2452
|
+
hasLoadError = false;
|
|
2453
|
+
router = inject(Router);
|
|
2454
|
+
tabService = inject(TabService);
|
|
2455
|
+
//#endregion
|
|
2456
|
+
//#region Properties
|
|
2457
|
+
get componentName() {
|
|
2458
|
+
return DefaultDetailsTabViewComponent.name;
|
|
2459
|
+
}
|
|
2460
|
+
//#endregion
|
|
2461
|
+
//#region Constructor and Angular life cycle methods
|
|
2462
|
+
ngOnInit() {
|
|
2463
|
+
super.ngOnInit();
|
|
2464
|
+
this.detailsViewRoute.set(RouteHelper.getRouteWithComponent(this.router.routerState.root.snapshot, DefaultDetailsTabViewComponent));
|
|
2465
|
+
this.hasEntityID = this.dataProviderService?.hasEntityID ?? null;
|
|
2466
|
+
this.dataProviderService
|
|
2467
|
+
?.getModel$()
|
|
2468
|
+
.pipe(takeUntil(this.destroy$))
|
|
2469
|
+
.subscribe((model) => {
|
|
2470
|
+
if (this.hasEntityID !== this.dataProviderService.hasEntityID || !this.detailsViewRoute()) {
|
|
2471
|
+
// If the entity ID changes, we need update the detailsViewRoute to get the correct route URL later.
|
|
2472
|
+
this.hasEntityID = this.dataProviderService.hasEntityID;
|
|
2473
|
+
this.detailsViewRoute.set(RouteHelper.getRouteWithComponent(this.router.routerState.root.snapshot, DefaultDetailsTabViewComponent));
|
|
2474
|
+
}
|
|
2475
|
+
const detailsViewRoute = this.detailsViewRoute();
|
|
2476
|
+
if (detailsViewRoute) {
|
|
2477
|
+
let title = detailsViewRoute.data['defaultTitle'];
|
|
2478
|
+
if (this.dataProviderService.hasEntityID) {
|
|
2479
|
+
title = this.dataProviderService.getTitle(model);
|
|
2480
|
+
}
|
|
2481
|
+
this.containerIcon.set('');
|
|
2482
|
+
this.containerTitle.set(title);
|
|
2483
|
+
const url = RouteHelper.getRouteURL(detailsViewRoute);
|
|
2484
|
+
this.tabService.updateTabTitle(url, title);
|
|
2485
|
+
}
|
|
2486
|
+
});
|
|
2487
|
+
this.dataProviderService
|
|
2488
|
+
?.getError$()
|
|
2489
|
+
.pipe(takeUntil(this.destroy$))
|
|
2490
|
+
.subscribe((error) => {
|
|
2491
|
+
this.hasLoadError = true;
|
|
2492
|
+
if (error.status === 404) {
|
|
2493
|
+
this.errorModal()?.title.set('Modal-NotFound-Title');
|
|
2494
|
+
this.errorModal()?.subtitle.set('');
|
|
2495
|
+
this.errorModal()?.showModal('Modal-NotFound-Message');
|
|
2496
|
+
}
|
|
2497
|
+
else {
|
|
2498
|
+
this.errorModal()?.title.set('Modal-Failed-Title');
|
|
2499
|
+
this.errorModal()?.subtitle.set('Modal-Failed-Administrator');
|
|
2500
|
+
this.errorModal()?.showModal(error);
|
|
2501
|
+
}
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
//#endregion
|
|
2505
|
+
//#region Event handlers
|
|
2506
|
+
onErrorModalClosed() {
|
|
2507
|
+
if (!this.hasLoadError) {
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
const history = this.tabService.activeTabHistory;
|
|
2511
|
+
const entityBaseUrl = history[history.length - 1]?.entityBaseUrl;
|
|
2512
|
+
// Find the entry before the invalid entity in the tab history.
|
|
2513
|
+
if (entityBaseUrl && history.length > 1) {
|
|
2514
|
+
const entityIndex = history.findIndex((entry) => entry.entityBaseUrl === entityBaseUrl);
|
|
2515
|
+
if (entityIndex > 0) {
|
|
2516
|
+
this.tabService.navigateCurrentTabBack(history[entityIndex - 1]);
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
this.tabService.closeActiveTab();
|
|
2521
|
+
}
|
|
2522
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: DefaultDetailsTabViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2523
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.7", type: DefaultDetailsTabViewComponent, isStandalone: true, selector: "framework-default-details-tab-view", providers: [
|
|
2524
|
+
{ provide: TabViewService },
|
|
2525
|
+
{ provide: DataProviderService, useFactory: dataProviderFactory, deps: [ActivatedRoute] },
|
|
2526
|
+
// A record's views are siblings: the form is one route, a child list is another, and only the
|
|
2527
|
+
// form provides a FormService of its own. So anything a non-form view renders has to find one
|
|
2528
|
+
// here or not at all -- and framework-button-edit asks for FormService and DataProviderService
|
|
2529
|
+
// together, of which this shell supplied only the second. Opening a child-list view threw
|
|
2530
|
+
// NG0201 before the component rendered at all.
|
|
2531
|
+
//
|
|
2532
|
+
// A form view's own provider shadows this one inside its subtree, so nothing changes there.
|
|
2533
|
+
// What reaches this instance is a view that has no form of its own, where the service sits
|
|
2534
|
+
// idle: the edit button reads `loading` and listens for `editCanceled`, and in modal mode --
|
|
2535
|
+
// which is why a child list has that button -- it never begins an edit on it.
|
|
2536
|
+
{ provide: FormService },
|
|
2537
|
+
], viewQueries: [{ propertyName: "errorModal", first: true, predicate: ErrorModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<lib-ribbon>\r\n <ng-container *ngTemplateOutlet=\"ribbonTemplate() ?? null\"></ng-container>\r\n\r\n <div right>\r\n <lib-ribbon-group [label]=\"'RibbonGroup-Page' | translate\">\r\n <framework-button-views [detailsViewRoute]=\"detailsViewRoute()\"> </framework-button-views>\r\n </lib-ribbon-group>\r\n </div>\r\n</lib-ribbon>\r\n\r\n<lib-group-container [icon]=\"containerIcon()\" [title]=\"containerTitle()\">\r\n <router-outlet> </router-outlet>\r\n</lib-group-container>\r\n\r\n<framework-error-modal (closed)=\"onErrorModalClosed()\"></framework-error-modal>\r\n", styles: [":host{display:flex;flex-grow:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow:hidden}router-outlet{display:none}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: ButtonViewsComponent, selector: "framework-button-views", inputs: ["detailsViewRoute"] }, { kind: "component", type: ErrorModalComponent, selector: "framework-error-modal", inputs: ["errorMessage", "size", "subtitle", "title"], outputs: ["errorMessageChange", "subtitleChange", "titleChange", "closed"] }, { kind: "component", type: GroupContainerComponent, selector: "lib-group-container", inputs: ["title", "icon"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: RibbonComponent, selector: "lib-ribbon" }, { kind: "component", type: RibbonGroupComponent, selector: "lib-ribbon-group", inputs: ["label"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2538
|
+
}
|
|
2539
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: DefaultDetailsTabViewComponent, decorators: [{
|
|
2540
|
+
type: Component,
|
|
2541
|
+
args: [{ selector: 'framework-default-details-tab-view', imports: [
|
|
2542
|
+
ButtonViewsComponent,
|
|
2543
|
+
ErrorModalComponent,
|
|
2544
|
+
GroupContainerComponent,
|
|
2545
|
+
NgTemplateOutlet,
|
|
2546
|
+
RibbonComponent,
|
|
2547
|
+
RibbonGroupComponent,
|
|
2548
|
+
RouterModule,
|
|
2549
|
+
TranslatePipe,
|
|
2550
|
+
], providers: [
|
|
2551
|
+
{ provide: TabViewService },
|
|
2552
|
+
{ provide: DataProviderService, useFactory: dataProviderFactory, deps: [ActivatedRoute] },
|
|
2553
|
+
// A record's views are siblings: the form is one route, a child list is another, and only the
|
|
2554
|
+
// form provides a FormService of its own. So anything a non-form view renders has to find one
|
|
2555
|
+
// here or not at all -- and framework-button-edit asks for FormService and DataProviderService
|
|
2556
|
+
// together, of which this shell supplied only the second. Opening a child-list view threw
|
|
2557
|
+
// NG0201 before the component rendered at all.
|
|
2558
|
+
//
|
|
2559
|
+
// A form view's own provider shadows this one inside its subtree, so nothing changes there.
|
|
2560
|
+
// What reaches this instance is a view that has no form of its own, where the service sits
|
|
2561
|
+
// idle: the edit button reads `loading` and listens for `editCanceled`, and in modal mode --
|
|
2562
|
+
// which is why a child list has that button -- it never begins an edit on it.
|
|
2563
|
+
{ provide: FormService },
|
|
2564
|
+
], template: "<lib-ribbon>\r\n <ng-container *ngTemplateOutlet=\"ribbonTemplate() ?? null\"></ng-container>\r\n\r\n <div right>\r\n <lib-ribbon-group [label]=\"'RibbonGroup-Page' | translate\">\r\n <framework-button-views [detailsViewRoute]=\"detailsViewRoute()\"> </framework-button-views>\r\n </lib-ribbon-group>\r\n </div>\r\n</lib-ribbon>\r\n\r\n<lib-group-container [icon]=\"containerIcon()\" [title]=\"containerTitle()\">\r\n <router-outlet> </router-outlet>\r\n</lib-group-container>\r\n\r\n<framework-error-modal (closed)=\"onErrorModalClosed()\"></framework-error-modal>\r\n", styles: [":host{display:flex;flex-grow:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow:hidden}router-outlet{display:none}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
2565
|
+
}], propDecorators: { errorModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ErrorModalComponent), { isSignal: true }] }] } });
|
|
2566
|
+
|
|
2567
|
+
class TabBreadcrumbsComponent {
|
|
2568
|
+
//#region ViewChilds, Inputs, Outputs
|
|
2569
|
+
//#endregion
|
|
2570
|
+
//#region Variables
|
|
2571
|
+
tabService = inject(TabService);
|
|
2572
|
+
//#endregion
|
|
2573
|
+
//#region Properties
|
|
2574
|
+
/**
|
|
2575
|
+
* Reads straight through to the service, which is the trap this component used to be the
|
|
2576
|
+
* example of: a getter over an array a service mutates elsewhere, with nothing to mark this
|
|
2577
|
+
* view dirty. TabService announces its own changes now, and that getter takes the dependency,
|
|
2578
|
+
* so no strategy is needed here.
|
|
2579
|
+
*/
|
|
2580
|
+
get tabHistory() {
|
|
2581
|
+
return this.tabService.activeTabHistory;
|
|
2582
|
+
}
|
|
2583
|
+
//#endregion
|
|
2584
|
+
//#region Constructor and Angular life cycle methods
|
|
2585
|
+
//#endregion
|
|
2586
|
+
//#region Event handlers
|
|
2587
|
+
onTitleClick(tab) {
|
|
2588
|
+
this.tabService.navigateCurrentTabBack(tab);
|
|
2589
|
+
}
|
|
2590
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabBreadcrumbsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2591
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: TabBreadcrumbsComponent, isStandalone: true, selector: "framework-tab-breadcrumbs", ngImport: i0, template: "<i class=\"fa-solid fa-angles-right\"></i>\r\n\r\n@for (history of tabHistory; track history) {\r\n <a\r\n role=\"button\"\r\n tabindex=\"0\"\r\n (click)=\"onTitleClick(history)\"\r\n (keydown.enter)=\"onTitleClick(history)\"\r\n (keydown.space)=\"onTitleClick(history); $event.preventDefault()\"\r\n >\r\n @if (!history.isTitleLoading) {\r\n <span>{{ history.title ?? '' | translate }}</span>\r\n }\r\n @if (history.isTitleLoading) {\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\r\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\r\n <path\r\n class=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n ></path>\r\n </svg>\r\n }\r\n </a>\r\n}\r\n", styles: ["@charset \"UTF-8\";@layer properties;:host{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing, .25rem);-webkit-user-select:none;user-select:none;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);border-bottom:1px solid var(--tabs-border);color:var(--tabs-item-text)}:host>i{flex-shrink:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:var(--tabs-close)}:host a{display:flex;cursor:pointer;align-items:center;gap:var(--spacing, .25rem);border-radius:var(--radius-sm, .25rem);padding-inline:var(--spacing, .25rem);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1))}:host a:hover,:host a:focus-visible{background-color:var(--tabs-item-hover-bg);color:var(--tabs-item-active-text)}:host a:focus-visible{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style: none;outline-style:none;--tw-ring-color: var(--tabs-item-active-text)}@media(forced-colors:active){:host a:focus-visible{outline:2px solid transparent;outline-offset:2px}}:host a:not(:first-of-type):before{margin-right:var(--spacing, .25rem);flex-shrink:0;--tw-content: \"\";content:var(--tw-content);width:0;height:0;border-top:.25rem solid transparent;border-bottom:.25rem solid transparent;border-left:.3rem solid currentColor;opacity:.45}:host a:last-of-type{color:var(--tabs-item-active-text)}:host a svg{display:inline-block;height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);animation:var(--animate-spin, spin 1s linear infinite)}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-ease{syntax: \"*\"; inherits: false;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-content{syntax: \"*\"; inherits: false; initial-value: \"\";}@keyframes spin{to{transform:rotate(360deg)}}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial;--tw-ease: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-content: \"\"}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2592
|
+
}
|
|
2593
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabBreadcrumbsComponent, decorators: [{
|
|
2594
|
+
type: Component,
|
|
2595
|
+
args: [{ selector: 'framework-tab-breadcrumbs', imports: [TranslatePipe], template: "<i class=\"fa-solid fa-angles-right\"></i>\r\n\r\n@for (history of tabHistory; track history) {\r\n <a\r\n role=\"button\"\r\n tabindex=\"0\"\r\n (click)=\"onTitleClick(history)\"\r\n (keydown.enter)=\"onTitleClick(history)\"\r\n (keydown.space)=\"onTitleClick(history); $event.preventDefault()\"\r\n >\r\n @if (!history.isTitleLoading) {\r\n <span>{{ history.title ?? '' | translate }}</span>\r\n }\r\n @if (history.isTitleLoading) {\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\r\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\r\n <path\r\n class=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n ></path>\r\n </svg>\r\n }\r\n </a>\r\n}\r\n", styles: ["@charset \"UTF-8\";@layer properties;:host{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing, .25rem);-webkit-user-select:none;user-select:none;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);border-bottom:1px solid var(--tabs-border);color:var(--tabs-item-text)}:host>i{flex-shrink:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:var(--tabs-close)}:host a{display:flex;cursor:pointer;align-items:center;gap:var(--spacing, .25rem);border-radius:var(--radius-sm, .25rem);padding-inline:var(--spacing, .25rem);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1))}:host a:hover,:host a:focus-visible{background-color:var(--tabs-item-hover-bg);color:var(--tabs-item-active-text)}:host a:focus-visible{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style: none;outline-style:none;--tw-ring-color: var(--tabs-item-active-text)}@media(forced-colors:active){:host a:focus-visible{outline:2px solid transparent;outline-offset:2px}}:host a:not(:first-of-type):before{margin-right:var(--spacing, .25rem);flex-shrink:0;--tw-content: \"\";content:var(--tw-content);width:0;height:0;border-top:.25rem solid transparent;border-bottom:.25rem solid transparent;border-left:.3rem solid currentColor;opacity:.45}:host a:last-of-type{color:var(--tabs-item-active-text)}:host a svg{display:inline-block;height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);animation:var(--animate-spin, spin 1s linear infinite)}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-ease{syntax: \"*\"; inherits: false;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-content{syntax: \"*\"; inherits: false; initial-value: \"\";}@keyframes spin{to{transform:rotate(360deg)}}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial;--tw-ease: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-content: \"\"}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
2596
|
+
}] });
|
|
2597
|
+
|
|
2598
|
+
/** Sub-pixel slack: scrollLeft is fractional, so an exact comparison never reaches the end. */
|
|
2599
|
+
const SCROLL_EPSILON = 1;
|
|
2600
|
+
/** How much of the visible strip one press of a scroll button travels. */
|
|
2601
|
+
const SCROLL_BUTTON_STEP = 0.8;
|
|
2602
|
+
class TabsComponent {
|
|
2603
|
+
ngZone;
|
|
2604
|
+
router;
|
|
2605
|
+
tabService;
|
|
2606
|
+
//#region ViewChilds, Inputs, Outputs
|
|
2607
|
+
tabsNav;
|
|
2608
|
+
//#endregion
|
|
2609
|
+
//#region Host listeners
|
|
2610
|
+
//#endregion
|
|
2611
|
+
//#region Variables
|
|
2612
|
+
static instances = 0;
|
|
2613
|
+
/** Unique per instance: the ids below are referenced by aria-controls/aria-labelledby. */
|
|
2614
|
+
instanceId = `framework-tabs-${TabsComponent.instances++}`;
|
|
2615
|
+
/**
|
|
2616
|
+
* Fingerprint of everything that can invalidate the strip's scroll position: which tab is
|
|
2617
|
+
* selected, how many there are, and the strip's own geometry. Deliberately does NOT include
|
|
2618
|
+
* scrollLeft -- the user scrolling by hand must not make us yank the strip back.
|
|
2619
|
+
*/
|
|
2620
|
+
lastStripSignature = '';
|
|
2621
|
+
navResizeObserver;
|
|
2622
|
+
isDestroyed = false;
|
|
2623
|
+
/** Drive the scroll buttons. Only ever assigned outside a change-detection pass (see below). */
|
|
2624
|
+
canScrollLeft = signal(false, /* @ts-ignore */
|
|
2625
|
+
...(ngDevMode ? [{ debugName: "canScrollLeft" }] : /* istanbul ignore next */ []));
|
|
2626
|
+
canScrollRight = signal(false, /* @ts-ignore */
|
|
2627
|
+
...(ngDevMode ? [{ debugName: "canScrollRight" }] : /* istanbul ignore next */ []));
|
|
2628
|
+
/**
|
|
2629
|
+
* Whether the strip holds more tabs than it has room for. This decides that the buttons exist;
|
|
2630
|
+
* canScrollLeft and canScrollRight only decide whether each one is enabled.
|
|
2631
|
+
*
|
|
2632
|
+
* They used to decide both, so a button vanished the moment you reached the end it pointed at.
|
|
2633
|
+
* The strip then changed width under the pointer, and a click could land after the button had
|
|
2634
|
+
* already unmounted and do nothing — which is what "sometimes it does not move" was.
|
|
2635
|
+
*/
|
|
2636
|
+
isOverflowing = signal(false, /* @ts-ignore */
|
|
2637
|
+
...(ngDevMode ? [{ debugName: "isOverflowing" }] : /* istanbul ignore next */ []));
|
|
2638
|
+
/**
|
|
2639
|
+
* Whether anything reaches the strip's right edge: a tab, or the right scroll button. While
|
|
2640
|
+
* something does, the panel's top-right corner is square, because the card's corner then belongs
|
|
2641
|
+
* to the strip. Rounding both leaves whatever sits at the edge standing over the sweep where the
|
|
2642
|
+
* panel has curved away and is not there yet — a piece floating past the corner.
|
|
2643
|
+
*/
|
|
2644
|
+
isFlushRight = signal(false, /* @ts-ignore */
|
|
2645
|
+
...(ngDevMode ? [{ debugName: "isFlushRight" }] : /* istanbul ignore next */ []));
|
|
2646
|
+
//#endregion
|
|
2647
|
+
//#region Properties
|
|
2648
|
+
get activeHistory() {
|
|
2649
|
+
return null; // this.tabService.activeHistory;
|
|
2650
|
+
}
|
|
2651
|
+
get activeTab() {
|
|
2652
|
+
return null; // this.tabService.activeTab;
|
|
2653
|
+
}
|
|
2654
|
+
get hasTabs() {
|
|
2655
|
+
return this.tabService.activeTabs.length > 0;
|
|
2656
|
+
}
|
|
2657
|
+
get openTabs() {
|
|
2658
|
+
return this.tabService.activeTabs ?? [];
|
|
2659
|
+
}
|
|
2660
|
+
get tabDisplayTitles() {
|
|
2661
|
+
return this.tabService.activeTabsDisplayTitles;
|
|
2662
|
+
}
|
|
2663
|
+
get tabLoadingStates() {
|
|
2664
|
+
return this.tabService.activeTabsLoadingStates;
|
|
2665
|
+
}
|
|
2666
|
+
/** Id of the panel these tabs control; referenced by each tab's aria-controls. */
|
|
2667
|
+
get panelId() {
|
|
2668
|
+
return `${this.instanceId}-panel`;
|
|
2669
|
+
}
|
|
2670
|
+
/** Id of the selected tab, so the panel can name the tab that labels it. */
|
|
2671
|
+
get activeTabId() {
|
|
2672
|
+
const index = this.openTabs.findIndex((tab) => this.isTabActive(tab));
|
|
2673
|
+
return index === -1 ? null : this.tabId(index);
|
|
2674
|
+
}
|
|
2675
|
+
trackByFn = (_, tab) => tab.url;
|
|
2676
|
+
//#endregion
|
|
2677
|
+
//#region Constructor and Angular life cycle methods
|
|
2678
|
+
constructor(ngZone, router, tabService) {
|
|
2679
|
+
this.ngZone = ngZone;
|
|
2680
|
+
this.router = router;
|
|
2681
|
+
this.tabService = tabService;
|
|
2682
|
+
// Replaces ngAfterViewChecked + queueMicrotask. That pair only ever ran because zone.js
|
|
2683
|
+
// produced an ambient pass on every event; with no zone there is nothing to piggyback on and
|
|
2684
|
+
// the strip silently stopped re-measuring. afterEveryRender is the supported hook for reading
|
|
2685
|
+
// layout, and it runs after the bindings have been checked, so writing the affordance here
|
|
2686
|
+
// cannot raise NG0100 either.
|
|
2687
|
+
afterEveryRender(() => {
|
|
2688
|
+
const signature = this.stripSignature();
|
|
2689
|
+
if (signature === this.lastStripSignature) {
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
this.lastStripSignature = signature;
|
|
2693
|
+
this.remeasureStrip();
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
ngAfterViewInit() {
|
|
2697
|
+
const nav = this.tabsNav?.nativeElement;
|
|
2698
|
+
if (!nav) {
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
// Outside Angular: these fire continuously while scrolling or dragging a window edge, and
|
|
2702
|
+
// none of them need a change-detection pass unless a button's state actually flips.
|
|
2703
|
+
this.ngZone.runOutsideAngular(() => {
|
|
2704
|
+
nav.addEventListener('scroll', this.onNavScroll, { passive: true });
|
|
2705
|
+
nav.addEventListener('wheel', this.onNavWheel, { passive: false });
|
|
2706
|
+
// Catches the window resizing and the sidebar collapsing -- both change how many tabs
|
|
2707
|
+
// fit without changing the tab list, so nothing else would tell us to re-check.
|
|
2708
|
+
if (typeof ResizeObserver === 'function') {
|
|
2709
|
+
this.navResizeObserver = new ResizeObserver(() => this.remeasureStrip());
|
|
2710
|
+
this.navResizeObserver.observe(nav);
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
ngOnDestroy() {
|
|
2715
|
+
this.isDestroyed = true;
|
|
2716
|
+
this.navResizeObserver?.disconnect();
|
|
2717
|
+
const nav = this.tabsNav?.nativeElement;
|
|
2718
|
+
nav?.removeEventListener('scroll', this.onNavScroll);
|
|
2719
|
+
nav?.removeEventListener('wheel', this.onNavWheel);
|
|
2720
|
+
}
|
|
2721
|
+
ngOnInit() {
|
|
2722
|
+
const activatedDetailsTabView = RouteHelper.getRouteByData(this.router.routerState.snapshot.root, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
|
|
2723
|
+
if (activatedDetailsTabView) {
|
|
2724
|
+
const shouldIgnoreFirstChildRoute = !!activatedDetailsTabView.firstChild &&
|
|
2725
|
+
activatedDetailsTabView.firstChild.data &&
|
|
2726
|
+
activatedDetailsTabView.firstChild.data['ignoreRoute'] === true;
|
|
2727
|
+
const url = RouteHelper.getRouteURL(activatedDetailsTabView);
|
|
2728
|
+
const clones = [];
|
|
2729
|
+
if (shouldIgnoreFirstChildRoute) {
|
|
2730
|
+
clones.push(RouteHelper.getRouteURL(activatedDetailsTabView.firstChild));
|
|
2731
|
+
}
|
|
2732
|
+
else if (activatedDetailsTabView.firstChild) {
|
|
2733
|
+
const childPath = activatedDetailsTabView.firstChild.url.map((s) => s.path).join('/');
|
|
2734
|
+
const defaultChildPath = activatedDetailsTabView.routeConfig?.children?.filter((route) => !!route.data && route.data['ignoreRoute'] !== true)?.[0]?.path ?? '';
|
|
2735
|
+
if (childPath !== defaultChildPath) {
|
|
2736
|
+
const entityID = Number(activatedDetailsTabView.paramMap.get('id'));
|
|
2737
|
+
if (!entityID) {
|
|
2738
|
+
// New entity: inner views are not available, redirect to base URL.
|
|
2739
|
+
this.router.navigate([url], { replaceUrl: true });
|
|
2740
|
+
}
|
|
2741
|
+
else {
|
|
2742
|
+
// Non-default child route (e.g. direct navigation to /audit): add sub-view to history for breadcrumbs.
|
|
2743
|
+
const childTitle = activatedDetailsTabView.firstChild?.data?.['title'] ?? '';
|
|
2744
|
+
queueMicrotask(() => {
|
|
2745
|
+
const childUrl = RouteHelper.getRouteURL(activatedDetailsTabView.firstChild);
|
|
2746
|
+
this.tabService.replaceCurrentTabSubView(url, new Tab({
|
|
2747
|
+
title: childTitle,
|
|
2748
|
+
url: childUrl,
|
|
2749
|
+
}));
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
this.tabService.openTab(new Tab({
|
|
2755
|
+
clones: clones,
|
|
2756
|
+
entityBaseUrl: url,
|
|
2757
|
+
queryParams: activatedDetailsTabView.queryParams,
|
|
2758
|
+
url: url,
|
|
2759
|
+
}));
|
|
2760
|
+
}
|
|
2761
|
+
else {
|
|
2762
|
+
const activatedTabView = RouteHelper.getRouteByData(this.router.routerState.snapshot.root, FRAMEWORK_VIEW_TYPE, FrameworkViewType.List);
|
|
2763
|
+
if (activatedTabView) {
|
|
2764
|
+
const url = RouteHelper.getRouteURL(activatedTabView);
|
|
2765
|
+
this.tabService.openTab(new Tab({
|
|
2766
|
+
queryParams: activatedTabView.queryParams,
|
|
2767
|
+
url: url,
|
|
2768
|
+
}));
|
|
2769
|
+
}
|
|
2770
|
+
else {
|
|
2771
|
+
this.router.navigate(['/']);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
//#endregion
|
|
2776
|
+
//#region Event handlers
|
|
2777
|
+
onActivateTab(tab) {
|
|
2778
|
+
this.tabService.activateTab(tab);
|
|
2779
|
+
}
|
|
2780
|
+
onCloseTab(index) {
|
|
2781
|
+
this.tabService.closeTab(index);
|
|
2782
|
+
}
|
|
2783
|
+
/** Middle click closes a tab, the way it does in a browser. */
|
|
2784
|
+
onTabAuxClick(event, index) {
|
|
2785
|
+
if (event.button !== 1) {
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
// Chromium starts autoscroll on middle click unless the default is prevented.
|
|
2789
|
+
event.preventDefault();
|
|
2790
|
+
this.onCloseTab(index);
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Roving focus. A tablist is a single tab stop: Tab reaches the selected tab, and the
|
|
2794
|
+
* arrow keys move between tabs from there. A delta of 0 means go to `from` exactly,
|
|
2795
|
+
* which is what Home and End want.
|
|
2796
|
+
*/
|
|
2797
|
+
onMoveFocus(from, delta) {
|
|
2798
|
+
const count = this.openTabs.length;
|
|
2799
|
+
if (count === 0) {
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
const target = delta === 0 ? Math.min(Math.max(from, 0), count - 1) : (from + delta + count) % count;
|
|
2803
|
+
this.focusTab(target);
|
|
2804
|
+
}
|
|
2805
|
+
/** The scroll buttons at each end of the strip. `direction` is -1 for left, 1 for right. */
|
|
2806
|
+
onScrollStrip(direction) {
|
|
2807
|
+
const nav = this.tabsNav?.nativeElement;
|
|
2808
|
+
if (!nav) {
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
const step = nav.clientWidth * SCROLL_BUTTON_STEP * direction;
|
|
2812
|
+
if (typeof nav.scrollBy === 'function') {
|
|
2813
|
+
nav.scrollBy({ left: step, behavior: 'smooth' });
|
|
2814
|
+
}
|
|
2815
|
+
else {
|
|
2816
|
+
// jsdom, and any browser without smooth scrolling, still gets the movement.
|
|
2817
|
+
nav.scrollLeft += step;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
//#endregion
|
|
2821
|
+
//#region Public methods
|
|
2822
|
+
isTabActive(tab) {
|
|
2823
|
+
return this.tabService.isTabActive(tab);
|
|
2824
|
+
}
|
|
2825
|
+
//#endregion
|
|
2826
|
+
//#region Private methods
|
|
2827
|
+
/** Stable per index, so aria-controls and aria-labelledby have something to point at. */
|
|
2828
|
+
tabId(index) {
|
|
2829
|
+
return `${this.instanceId}-tab-${index}`;
|
|
2830
|
+
}
|
|
2831
|
+
tabElements() {
|
|
2832
|
+
return Array.from(this.tabsNav?.nativeElement.querySelectorAll('[role="tab"]') ?? []);
|
|
2833
|
+
}
|
|
2834
|
+
/** Moves focus AND selection: this tablist follows the automatic-activation pattern. */
|
|
2835
|
+
focusTab(index) {
|
|
2836
|
+
const element = this.tabElements()[index];
|
|
2837
|
+
if (!element) {
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2840
|
+
element.focus();
|
|
2841
|
+
const tab = this.openTabs[index];
|
|
2842
|
+
if (tab) {
|
|
2843
|
+
this.onActivateTab(tab);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Everything that invalidates the scroll position, in one cheap string. The geometry is part
|
|
2848
|
+
* of it because the window resizing or the sidebar collapsing can push the selected tab out
|
|
2849
|
+
* of sight without the tab list changing at all -- which is precisely how you end up looking
|
|
2850
|
+
* at a panel whose tab is nowhere on screen.
|
|
2851
|
+
*/
|
|
2852
|
+
stripSignature() {
|
|
2853
|
+
const nav = this.tabsNav?.nativeElement;
|
|
2854
|
+
const index = this.openTabs.findIndex((tab) => this.isTabActive(tab));
|
|
2855
|
+
return `${index}|${this.openTabs.length}|${nav?.clientWidth ?? 0}|${nav?.scrollWidth ?? 0}`;
|
|
2856
|
+
}
|
|
2857
|
+
/** Keeps the selected tab on screen. Called from a microtask, never mid-change-detection. */
|
|
2858
|
+
scrollActiveTabIntoView() {
|
|
2859
|
+
const index = this.openTabs.findIndex((tab) => this.isTabActive(tab));
|
|
2860
|
+
if (index === -1) {
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
// Guarded: scrollIntoView is not implemented in jsdom, so an unguarded call throws in
|
|
2864
|
+
// any consumer's unit tests.
|
|
2865
|
+
const element = this.tabElements()[index];
|
|
2866
|
+
if (typeof element?.scrollIntoView === 'function') {
|
|
2867
|
+
element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Recomputes whether either scroll button applies. Reads first and only enters the zone when
|
|
2872
|
+
* a button actually flips, so an ordinary scroll costs one layout read and no render.
|
|
2873
|
+
*/
|
|
2874
|
+
syncScrollAffordance() {
|
|
2875
|
+
const nav = this.tabsNav?.nativeElement;
|
|
2876
|
+
if (!nav || this.isDestroyed) {
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
const canLeft = nav.scrollLeft > SCROLL_EPSILON;
|
|
2880
|
+
const canRight = nav.scrollLeft + nav.clientWidth < nav.scrollWidth - SCROLL_EPSILON;
|
|
2881
|
+
const overflowing = nav.scrollWidth > nav.clientWidth + SCROLL_EPSILON;
|
|
2882
|
+
// Overflowing means the right button holds the edge. Short of that, the last tab does if it
|
|
2883
|
+
// ends within a pixel of the strip — the case where the tabs happen to fill it exactly.
|
|
2884
|
+
const lastTab = nav.lastElementChild;
|
|
2885
|
+
const flushRight = overflowing ||
|
|
2886
|
+
(lastTab !== null && lastTab.getBoundingClientRect().right >= nav.getBoundingClientRect().right - SCROLL_EPSILON);
|
|
2887
|
+
if (canLeft === this.canScrollLeft() &&
|
|
2888
|
+
canRight === this.canScrollRight() &&
|
|
2889
|
+
overflowing === this.isOverflowing() &&
|
|
2890
|
+
flushRight === this.isFlushRight()) {
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
this.canScrollLeft.set(canLeft);
|
|
2894
|
+
this.canScrollRight.set(canRight);
|
|
2895
|
+
this.isOverflowing.set(overflowing);
|
|
2896
|
+
this.isFlushRight.set(flushRight);
|
|
2897
|
+
}
|
|
2898
|
+
/** Everything a geometry change implies: the selected tab may be off-screen, and the scroll
|
|
2899
|
+
* buttons may have appeared or gone. */
|
|
2900
|
+
remeasureStrip() {
|
|
2901
|
+
this.scrollActiveTabIntoView();
|
|
2902
|
+
this.syncScrollAffordance();
|
|
2903
|
+
}
|
|
2904
|
+
/** Arrow function: registered and removed as a listener, so it must keep its `this`. */
|
|
2905
|
+
onNavScroll = () => {
|
|
2906
|
+
this.syncScrollAffordance();
|
|
2907
|
+
};
|
|
2908
|
+
/**
|
|
2909
|
+
* A tab strip is horizontal but a mouse wheel is vertical, so without this the only way to
|
|
2910
|
+
* reach an off-screen tab with a plain wheel mouse is the buttons. Trackpads and shift+wheel
|
|
2911
|
+
* already produce deltaX, so those are left alone.
|
|
2912
|
+
*/
|
|
2913
|
+
onNavWheel = (event) => {
|
|
2914
|
+
const nav = this.tabsNav?.nativeElement;
|
|
2915
|
+
if (!nav || event.deltaX !== 0 || event.deltaY === 0) {
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
if (nav.scrollWidth <= nav.clientWidth) {
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
event.preventDefault();
|
|
2922
|
+
nav.scrollLeft += event.deltaY;
|
|
2923
|
+
};
|
|
2924
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabsComponent, deps: [{ token: i0.NgZone }, { token: i1$1.Router }, { token: TabService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2925
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: TabsComponent, isStandalone: true, selector: "framework-tabs", viewQueries: [{ propertyName: "tabsNav", first: true, predicate: ["tabsNav"], descendants: true }], ngImport: i0, template: "@if (hasTabs) {\r\n <div class=\"tabs-container\" [class.can-scroll-left]=\"canScrollLeft()\" [class.can-scroll-right]=\"canScrollRight()\">\r\n <!-- Both buttons are present whenever the strip overflows, and disabled rather than removed at\r\n the end they point at. Rendering them per-direction meant the one being reached for\r\n disappeared on arrival: the strip changed width under the pointer and the click landed on\r\n nothing. tabindex -1 because the tablist is already one tab stop and the arrow keys reach\r\n every tab -- these are a pointer affordance, not a second keyboard path. -->\r\n @if (isOverflowing()) {\r\n <button\r\n class=\"tabs-scroll left\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [disabled]=\"!canScrollLeft()\"\r\n [attr.aria-label]=\"'Tabs-ScrollLeft' | translate\"\r\n (click)=\"onScrollStrip(-1)\"\r\n >\r\n <i class=\"fas fa-chevron-left\"></i>\r\n </button>\r\n }\r\n\r\n <!-- Horizontally scrollable. This used to be overflow:hidden, so once the tabs were\r\n wider than the bar the ones past the right edge could not be reached at all. -->\r\n <ul #tabsNav class=\"tabs-nav\" role=\"tablist\">\r\n @for (tab of openTabs; track trackByFn(i, tab); let i = $index) {\r\n <li role=\"presentation\" [class.active]=\"isTabActive(tab)\">\r\n <a\r\n class=\"tab-item\"\r\n role=\"tab\"\r\n [id]=\"tabId(i)\"\r\n [attr.aria-controls]=\"panelId\"\r\n [attr.aria-selected]=\"isTabActive(tab)\"\r\n [attr.title]=\"tabDisplayTitles[i] ?? tab.title ?? null\"\r\n [attr.tabindex]=\"isTabActive(tab) ? 0 : -1\"\r\n (click)=\"onActivateTab(tab)\"\r\n (auxclick)=\"onTabAuxClick($event, i)\"\r\n (keydown.enter)=\"onActivateTab(tab)\"\r\n (keydown.space)=\"onActivateTab(tab); $event.preventDefault()\"\r\n (keydown.arrowleft)=\"onMoveFocus(i, -1); $event.preventDefault()\"\r\n (keydown.arrowright)=\"onMoveFocus(i, 1); $event.preventDefault()\"\r\n (keydown.home)=\"onMoveFocus(0, 0); $event.preventDefault()\"\r\n (keydown.end)=\"onMoveFocus(openTabs.length - 1, 0); $event.preventDefault()\"\r\n (keydown.delete)=\"onCloseTab(i); $event.preventDefault()\"\r\n >\r\n <!-- Rendered whether or not the title has resolved, so the tab keeps its width\r\n and the strip does not reflow as each title arrives. -->\r\n <span class=\"label\">{{ tabDisplayTitles[i] ?? tab.title ?? '' | translate }}</span>\r\n\r\n @if (tabLoadingStates[i]) {\r\n <svg class=\"spinner\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\r\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\r\n <path\r\n class=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n ></path>\r\n </svg>\r\n }\r\n </a>\r\n\r\n <!-- tabindex -1: the tablist is one tab stop, and the close control is reachable\r\n from the tab itself with Delete rather than by tabbing through every one. -->\r\n <button\r\n class=\"tab-close\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'Tabs-CloseTab' | translate: { title: tabDisplayTitles[i] ?? tab.title ?? '' }\"\r\n (click)=\"onCloseTab(i)\"\r\n >\r\n <i class=\"fas fa-times\"></i>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (isOverflowing()) {\r\n <button\r\n class=\"tabs-scroll right\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [disabled]=\"!canScrollRight()\"\r\n [attr.aria-label]=\"'Tabs-ScrollRight' | translate\"\r\n (click)=\"onScrollStrip(1)\"\r\n >\r\n <i class=\"fas fa-chevron-right\"></i>\r\n </button>\r\n }\r\n </div>\r\n}\r\n@if (hasTabs) {\r\n <div\r\n class=\"tab-content\"\r\n [class.flush-right]=\"isFlushRight()\"\r\n role=\"tabpanel\"\r\n [id]=\"panelId\"\r\n [attr.aria-labelledby]=\"activeTabId\"\r\n >\r\n <framework-tab-breadcrumbs></framework-tab-breadcrumbs>\r\n <ng-content></ng-content>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";@layer properties;:host{position:relative;display:block;height:100%;max-height:100%;max-width:100%}.tabs-container{position:absolute;top:0;z-index:1;display:flex;width:100%;max-width:100%;flex-direction:row;align-items:flex-end;height:var(--tabs-height);max-height:var(--tabs-height);--tabs-fade-start: 0px;--tabs-fade-end: 0px}.tabs-container.can-scroll-left{--tabs-fade-start: var(--tabs-fade-width)}.tabs-container.can-scroll-right{--tabs-fade-end: var(--tabs-fade-width)}.tabs-container .tabs-scroll{display:flex;flex-shrink:0;align-items:center;justify-content:center;align-self:stretch;cursor:pointer;--tw-border-style: none;border-style:none;padding:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: 1;line-height:1;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));width:var(--tabs-scroll-width);background-color:var(--tabs-item-bg);color:var(--tabs-item-text)}.tabs-container .tabs-scroll.left{border-top-left-radius:var(--tabs-item-radius)}.tabs-container .tabs-scroll.right{border-top-right-radius:var(--tabs-radius)}.tabs-container .tabs-scroll:hover:not(:disabled){background-color:var(--tabs-item-hover-bg);color:var(--tabs-item-active-text)}.tabs-container .tabs-scroll:disabled{cursor:default;opacity:40%}.tabs-container .tabs-nav{display:flex;height:100%;flex-direction:row;align-items:flex-end;min-width:0px;flex:1;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;border-top-right-radius:var(--tabs-radius);scrollbar-width:none}.tabs-container .tabs-nav::-webkit-scrollbar{display:none}.tabs-container .tabs-nav{mask-image:linear-gradient(to right,transparent 0,black var(--tabs-fade-start),black calc(100% - var(--tabs-fade-end)),transparent 100%);-webkit-mask-image:linear-gradient(to right,transparent 0,black var(--tabs-fade-start),black calc(100% - var(--tabs-fade-end)),transparent 100%)}.tabs-container .tabs-nav li{position:relative;display:flex;flex-shrink:1;flex-grow:0;min-width:var(--tabs-item-min-width)}.tabs-container .tabs-nav li.active{z-index:1}.tabs-container .tabs-nav li .tab-item{display:flex;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding-block:calc(var(--spacing, .25rem) * 2);padding-right:calc(var(--spacing, .25rem) * 10);padding-left:calc(var(--spacing, .25rem) * 4);-webkit-user-select:none;user-select:none;overflow:hidden;font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));min-width:0px;flex:1;max-width:var(--tabs-item-max-width);background-color:var(--tabs-item-bg);color:var(--tabs-item-text);border-top-left-radius:var(--tabs-item-radius);border-top-right-radius:var(--tabs-item-radius)}.tabs-container .tabs-nav li .tab-item .label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tabs-container .tabs-nav li .tab-item .spinner{height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);flex-shrink:0;animation:var(--animate-spin, spin 1s linear infinite)}.tabs-container .tabs-nav li .tab-item:focus-visible{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style: none;outline-style:none;--tw-ring-inset: inset;--tw-ring-color: var(--tabs-item-active-text)}@media(forced-colors:active){.tabs-container .tabs-nav li .tab-item:focus-visible{outline:2px solid transparent;outline-offset:2px}}.tabs-container .tabs-nav li:hover .tab-item{background-color:var(--tabs-item-hover-bg)}.tabs-container .tabs-nav li.active .tab-item{background-color:var(--tabs-item-active-bg);color:var(--tabs-item-active-text);backdrop-filter:blur(var(--tabs-blur));-webkit-backdrop-filter:blur(var(--tabs-blur))}.tabs-container .tabs-nav li .tab-close{position:absolute;top:50%;right:calc(var(--spacing, .25rem) * 1.5);display:flex;--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y);align-items:center;justify-content:center;height:calc(var(--spacing, .25rem) * 6);width:calc(var(--spacing, .25rem) * 6);cursor:pointer;border-radius:calc(infinity * 1px);font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-leading: 1;line-height:1;--tw-border-style: none;border-style:none;background-color:transparent;padding:0;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));color:var(--tabs-close)}.tabs-container .tabs-nav li .tab-close:hover,.tabs-container .tabs-nav li .tab-close:focus-visible{background-color:var(--tabs-close-hover-bg);color:var(--tabs-close-hover)}.tab-content{position:absolute;bottom:0;display:flex;width:100%;max-width:100%;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow-y:auto;padding:calc(var(--spacing, .25rem) * 2);top:var(--tabs-height)}.tab-content ::ng-deep router-outlet{display:none}.tab-content{border:1px solid var(--tabs-border);border-top:0;border-bottom-left-radius:var(--tabs-radius);border-bottom-right-radius:var(--tabs-radius);border-top-right-radius:var(--tabs-radius);box-shadow:var(--tabs-shadow)}.tab-content.flush-right{border-top-right-radius:0}.tab-content{--surface-radius: calc(var(--tabs-radius) - .5rem - 1px);isolation:isolate}.tab-content:before{pointer-events:none;position:absolute;inset:0;z-index:-1;--tw-content: \"\";content:var(--tw-content);background-color:var(--tabs-content-bg);border-radius:inherit;backdrop-filter:blur(var(--tabs-blur));-webkit-backdrop-filter:blur(var(--tabs-blur))}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-ease{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-translate-x{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-translate-y{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-translate-z{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-content{syntax: \"*\"; inherits: false; initial-value: \"\";}@keyframes spin{to{transform:rotate(360deg)}}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading: initial;--tw-ease: initial;--tw-font-weight: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-translate-x: 0;--tw-translate-y: 0;--tw-translate-z: 0;--tw-content: \"\"}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: TabBreadcrumbsComponent, selector: "framework-tab-breadcrumbs" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2926
|
+
}
|
|
2927
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabsComponent, decorators: [{
|
|
2928
|
+
type: Component,
|
|
2929
|
+
args: [{ selector: 'framework-tabs', imports: [TabBreadcrumbsComponent, TranslatePipe], template: "@if (hasTabs) {\r\n <div class=\"tabs-container\" [class.can-scroll-left]=\"canScrollLeft()\" [class.can-scroll-right]=\"canScrollRight()\">\r\n <!-- Both buttons are present whenever the strip overflows, and disabled rather than removed at\r\n the end they point at. Rendering them per-direction meant the one being reached for\r\n disappeared on arrival: the strip changed width under the pointer and the click landed on\r\n nothing. tabindex -1 because the tablist is already one tab stop and the arrow keys reach\r\n every tab -- these are a pointer affordance, not a second keyboard path. -->\r\n @if (isOverflowing()) {\r\n <button\r\n class=\"tabs-scroll left\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [disabled]=\"!canScrollLeft()\"\r\n [attr.aria-label]=\"'Tabs-ScrollLeft' | translate\"\r\n (click)=\"onScrollStrip(-1)\"\r\n >\r\n <i class=\"fas fa-chevron-left\"></i>\r\n </button>\r\n }\r\n\r\n <!-- Horizontally scrollable. This used to be overflow:hidden, so once the tabs were\r\n wider than the bar the ones past the right edge could not be reached at all. -->\r\n <ul #tabsNav class=\"tabs-nav\" role=\"tablist\">\r\n @for (tab of openTabs; track trackByFn(i, tab); let i = $index) {\r\n <li role=\"presentation\" [class.active]=\"isTabActive(tab)\">\r\n <a\r\n class=\"tab-item\"\r\n role=\"tab\"\r\n [id]=\"tabId(i)\"\r\n [attr.aria-controls]=\"panelId\"\r\n [attr.aria-selected]=\"isTabActive(tab)\"\r\n [attr.title]=\"tabDisplayTitles[i] ?? tab.title ?? null\"\r\n [attr.tabindex]=\"isTabActive(tab) ? 0 : -1\"\r\n (click)=\"onActivateTab(tab)\"\r\n (auxclick)=\"onTabAuxClick($event, i)\"\r\n (keydown.enter)=\"onActivateTab(tab)\"\r\n (keydown.space)=\"onActivateTab(tab); $event.preventDefault()\"\r\n (keydown.arrowleft)=\"onMoveFocus(i, -1); $event.preventDefault()\"\r\n (keydown.arrowright)=\"onMoveFocus(i, 1); $event.preventDefault()\"\r\n (keydown.home)=\"onMoveFocus(0, 0); $event.preventDefault()\"\r\n (keydown.end)=\"onMoveFocus(openTabs.length - 1, 0); $event.preventDefault()\"\r\n (keydown.delete)=\"onCloseTab(i); $event.preventDefault()\"\r\n >\r\n <!-- Rendered whether or not the title has resolved, so the tab keeps its width\r\n and the strip does not reflow as each title arrives. -->\r\n <span class=\"label\">{{ tabDisplayTitles[i] ?? tab.title ?? '' | translate }}</span>\r\n\r\n @if (tabLoadingStates[i]) {\r\n <svg class=\"spinner\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\r\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\r\n <path\r\n class=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n ></path>\r\n </svg>\r\n }\r\n </a>\r\n\r\n <!-- tabindex -1: the tablist is one tab stop, and the close control is reachable\r\n from the tab itself with Delete rather than by tabbing through every one. -->\r\n <button\r\n class=\"tab-close\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'Tabs-CloseTab' | translate: { title: tabDisplayTitles[i] ?? tab.title ?? '' }\"\r\n (click)=\"onCloseTab(i)\"\r\n >\r\n <i class=\"fas fa-times\"></i>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (isOverflowing()) {\r\n <button\r\n class=\"tabs-scroll right\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [disabled]=\"!canScrollRight()\"\r\n [attr.aria-label]=\"'Tabs-ScrollRight' | translate\"\r\n (click)=\"onScrollStrip(1)\"\r\n >\r\n <i class=\"fas fa-chevron-right\"></i>\r\n </button>\r\n }\r\n </div>\r\n}\r\n@if (hasTabs) {\r\n <div\r\n class=\"tab-content\"\r\n [class.flush-right]=\"isFlushRight()\"\r\n role=\"tabpanel\"\r\n [id]=\"panelId\"\r\n [attr.aria-labelledby]=\"activeTabId\"\r\n >\r\n <framework-tab-breadcrumbs></framework-tab-breadcrumbs>\r\n <ng-content></ng-content>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";@layer properties;:host{position:relative;display:block;height:100%;max-height:100%;max-width:100%}.tabs-container{position:absolute;top:0;z-index:1;display:flex;width:100%;max-width:100%;flex-direction:row;align-items:flex-end;height:var(--tabs-height);max-height:var(--tabs-height);--tabs-fade-start: 0px;--tabs-fade-end: 0px}.tabs-container.can-scroll-left{--tabs-fade-start: var(--tabs-fade-width)}.tabs-container.can-scroll-right{--tabs-fade-end: var(--tabs-fade-width)}.tabs-container .tabs-scroll{display:flex;flex-shrink:0;align-items:center;justify-content:center;align-self:stretch;cursor:pointer;--tw-border-style: none;border-style:none;padding:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: 1;line-height:1;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));width:var(--tabs-scroll-width);background-color:var(--tabs-item-bg);color:var(--tabs-item-text)}.tabs-container .tabs-scroll.left{border-top-left-radius:var(--tabs-item-radius)}.tabs-container .tabs-scroll.right{border-top-right-radius:var(--tabs-radius)}.tabs-container .tabs-scroll:hover:not(:disabled){background-color:var(--tabs-item-hover-bg);color:var(--tabs-item-active-text)}.tabs-container .tabs-scroll:disabled{cursor:default;opacity:40%}.tabs-container .tabs-nav{display:flex;height:100%;flex-direction:row;align-items:flex-end;min-width:0px;flex:1;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;border-top-right-radius:var(--tabs-radius);scrollbar-width:none}.tabs-container .tabs-nav::-webkit-scrollbar{display:none}.tabs-container .tabs-nav{mask-image:linear-gradient(to right,transparent 0,black var(--tabs-fade-start),black calc(100% - var(--tabs-fade-end)),transparent 100%);-webkit-mask-image:linear-gradient(to right,transparent 0,black var(--tabs-fade-start),black calc(100% - var(--tabs-fade-end)),transparent 100%)}.tabs-container .tabs-nav li{position:relative;display:flex;flex-shrink:1;flex-grow:0;min-width:var(--tabs-item-min-width)}.tabs-container .tabs-nav li.active{z-index:1}.tabs-container .tabs-nav li .tab-item{display:flex;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding-block:calc(var(--spacing, .25rem) * 2);padding-right:calc(var(--spacing, .25rem) * 10);padding-left:calc(var(--spacing, .25rem) * 4);-webkit-user-select:none;user-select:none;overflow:hidden;font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));min-width:0px;flex:1;max-width:var(--tabs-item-max-width);background-color:var(--tabs-item-bg);color:var(--tabs-item-text);border-top-left-radius:var(--tabs-item-radius);border-top-right-radius:var(--tabs-item-radius)}.tabs-container .tabs-nav li .tab-item .label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tabs-container .tabs-nav li .tab-item .spinner{height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);flex-shrink:0;animation:var(--animate-spin, spin 1s linear infinite)}.tabs-container .tabs-nav li .tab-item:focus-visible{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style: none;outline-style:none;--tw-ring-inset: inset;--tw-ring-color: var(--tabs-item-active-text)}@media(forced-colors:active){.tabs-container .tabs-nav li .tab-item:focus-visible{outline:2px solid transparent;outline-offset:2px}}.tabs-container .tabs-nav li:hover .tab-item{background-color:var(--tabs-item-hover-bg)}.tabs-container .tabs-nav li.active .tab-item{background-color:var(--tabs-item-active-bg);color:var(--tabs-item-active-text);backdrop-filter:blur(var(--tabs-blur));-webkit-backdrop-filter:blur(var(--tabs-blur))}.tabs-container .tabs-nav li .tab-close{position:absolute;top:50%;right:calc(var(--spacing, .25rem) * 1.5);display:flex;--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y);align-items:center;justify-content:center;height:calc(var(--spacing, .25rem) * 6);width:calc(var(--spacing, .25rem) * 6);cursor:pointer;border-radius:calc(infinity * 1px);font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-leading: 1;line-height:1;--tw-border-style: none;border-style:none;background-color:transparent;padding:0;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));color:var(--tabs-close)}.tabs-container .tabs-nav li .tab-close:hover,.tabs-container .tabs-nav li .tab-close:focus-visible{background-color:var(--tabs-close-hover-bg);color:var(--tabs-close-hover)}.tab-content{position:absolute;bottom:0;display:flex;width:100%;max-width:100%;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow-y:auto;padding:calc(var(--spacing, .25rem) * 2);top:var(--tabs-height)}.tab-content ::ng-deep router-outlet{display:none}.tab-content{border:1px solid var(--tabs-border);border-top:0;border-bottom-left-radius:var(--tabs-radius);border-bottom-right-radius:var(--tabs-radius);border-top-right-radius:var(--tabs-radius);box-shadow:var(--tabs-shadow)}.tab-content.flush-right{border-top-right-radius:0}.tab-content{--surface-radius: calc(var(--tabs-radius) - .5rem - 1px);isolation:isolate}.tab-content:before{pointer-events:none;position:absolute;inset:0;z-index:-1;--tw-content: \"\";content:var(--tw-content);background-color:var(--tabs-content-bg);border-radius:inherit;backdrop-filter:blur(var(--tabs-blur));-webkit-backdrop-filter:blur(var(--tabs-blur))}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-ease{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-translate-x{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-translate-y{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-translate-z{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-content{syntax: \"*\"; inherits: false; initial-value: \"\";}@keyframes spin{to{transform:rotate(360deg)}}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading: initial;--tw-ease: initial;--tw-font-weight: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-translate-x: 0;--tw-translate-y: 0;--tw-translate-z: 0;--tw-content: \"\"}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
2930
|
+
}], ctorParameters: () => [{ type: i0.NgZone }, { type: i1$1.Router }, { type: TabService }], propDecorators: { tabsNav: [{
|
|
2931
|
+
type: ViewChild,
|
|
2932
|
+
args: ['tabsNav']
|
|
2933
|
+
}] } });
|
|
2934
|
+
|
|
2935
|
+
/**
|
|
2936
|
+
* @deprecated Use standalone component imports instead of FrameworkViewsModule.
|
|
2937
|
+
* Import individual components directly in your component's `imports` array.
|
|
2938
|
+
*/
|
|
2939
|
+
class FrameworkViewsModule {
|
|
2940
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkViewsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2941
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkViewsModule, imports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent, TabBreadcrumbsComponent, TabsComponent], exports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent, TabsComponent] });
|
|
2942
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkViewsModule, imports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent] });
|
|
2943
|
+
}
|
|
2944
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkViewsModule, decorators: [{
|
|
2945
|
+
type: NgModule,
|
|
2946
|
+
args: [{
|
|
2947
|
+
declarations: [],
|
|
2948
|
+
imports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent, TabBreadcrumbsComponent, TabsComponent],
|
|
2949
|
+
exports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent, TabsComponent],
|
|
2950
|
+
}]
|
|
2951
|
+
}] });
|
|
2952
|
+
|
|
2953
|
+
/**
|
|
2954
|
+
* @deprecated Use standalone component imports instead of FrameworkComponentsModule.
|
|
2955
|
+
* Import individual components directly in your component's `imports` array.
|
|
2956
|
+
*/
|
|
2957
|
+
class FrameworkComponentsModule {
|
|
2958
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkComponentsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2959
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkComponentsModule, exports: [FrameworkButtonsModule, FrameworkModalsModule, FrameworkViewsModule] });
|
|
2960
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkComponentsModule, imports: [FrameworkButtonsModule, FrameworkModalsModule, FrameworkViewsModule] });
|
|
2961
|
+
}
|
|
2962
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkComponentsModule, decorators: [{
|
|
2963
|
+
type: NgModule,
|
|
2964
|
+
args: [{
|
|
2965
|
+
declarations: [],
|
|
2966
|
+
imports: [],
|
|
2967
|
+
exports: [FrameworkButtonsModule, FrameworkModalsModule, FrameworkViewsModule],
|
|
2968
|
+
}]
|
|
2969
|
+
}] });
|
|
2970
|
+
|
|
2971
|
+
const APP_CONFIG = new InjectionToken('Manually constructed AppConfig', {
|
|
2972
|
+
providedIn: 'root',
|
|
2973
|
+
factory: () => new AppConfig(''),
|
|
2974
|
+
});
|
|
2975
|
+
class AppConfig {
|
|
2976
|
+
BASE_URL;
|
|
2977
|
+
/** Application name shown in the top bar brand block. */
|
|
2978
|
+
appName;
|
|
2979
|
+
/** Company name shown beneath the application name. */
|
|
2980
|
+
companyName;
|
|
2981
|
+
/** Environment key (e.g. 'DEV', 'QA', 'STG', 'PROD'). Empty string when unset. */
|
|
2982
|
+
environment;
|
|
2983
|
+
/** Optional brand logo URL shown in the top bar. */
|
|
2984
|
+
logoUrl;
|
|
2985
|
+
/** Whether the top-bar notifications feature (bell + badge) is enabled. */
|
|
2986
|
+
notificationsEnabled;
|
|
2987
|
+
/** SignalR hub URL the notifications service connects to when enabled. */
|
|
2988
|
+
notificationsUrl;
|
|
2989
|
+
/** Application version string (e.g. '1.4.0'), shown at the bottom of the navigation. */
|
|
2990
|
+
version;
|
|
2991
|
+
constructor(baseUrl, options) {
|
|
2992
|
+
this.BASE_URL = baseUrl;
|
|
2993
|
+
this.appName = options?.appName ?? '';
|
|
2994
|
+
this.companyName = options?.companyName ?? '';
|
|
2995
|
+
this.environment = options?.environment ?? '';
|
|
2996
|
+
this.logoUrl = options?.logoUrl;
|
|
2997
|
+
this.notificationsEnabled = options?.notificationsEnabled ?? false;
|
|
2998
|
+
this.notificationsUrl = options?.notificationsUrl ?? '';
|
|
2999
|
+
this.version = options?.version ?? '';
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
class FrameworkGridConfigsProvider extends DataGridConfigsProvider {
|
|
3004
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3005
|
+
//#endregion
|
|
3006
|
+
//#region Variables
|
|
3007
|
+
//#endregion
|
|
3008
|
+
//#region Properties
|
|
3009
|
+
//#endregion
|
|
3010
|
+
//#region Constructor and Angular life cycle methods
|
|
3011
|
+
constructor() {
|
|
3012
|
+
super();
|
|
3013
|
+
Object.assign(this.configs, {
|
|
3014
|
+
loadingDisplayText: 'Grid-Loading',
|
|
3015
|
+
messageOnEmpty: 'Grid-Message-Empty',
|
|
3016
|
+
messageOnFailed: 'Grid-Message-Failed',
|
|
3017
|
+
messageOnLazyLoad: 'Grid-Message-LazyLoad',
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkGridConfigsProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3021
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkGridConfigsProvider });
|
|
3022
|
+
}
|
|
3023
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkGridConfigsProvider, decorators: [{
|
|
3024
|
+
type: Injectable
|
|
3025
|
+
}], ctorParameters: () => [] });
|
|
3026
|
+
|
|
3027
|
+
/**
|
|
3028
|
+
* @deprecated Use standalone component imports instead of FrameworkModule.
|
|
3029
|
+
* Import individual components directly in your component's `imports` array.
|
|
3030
|
+
*/
|
|
3031
|
+
class FrameworkModule {
|
|
3032
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
3033
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModule, exports: [FrameworkComponentsModule] });
|
|
3034
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModule, imports: [FrameworkComponentsModule] });
|
|
3035
|
+
}
|
|
3036
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModule, decorators: [{
|
|
3037
|
+
type: NgModule,
|
|
3038
|
+
args: [{
|
|
3039
|
+
declarations: [],
|
|
3040
|
+
imports: [],
|
|
3041
|
+
exports: [FrameworkComponentsModule],
|
|
3042
|
+
}]
|
|
3043
|
+
}] });
|
|
3044
|
+
|
|
3045
|
+
const ZAMBON_FRAMEWORK_I18N_ASSET_PATH = 'assets/i18n/zambon-dev/framework';
|
|
3046
|
+
const ZAMBON_FRAMEWORK_I18N_ASSET = {
|
|
3047
|
+
glob: '**/*.json',
|
|
3048
|
+
input: 'node_modules/@zambon-dev/ui/i18n/framework',
|
|
3049
|
+
output: ZAMBON_FRAMEWORK_I18N_ASSET_PATH,
|
|
3050
|
+
};
|
|
3051
|
+
const ZAMBON_FRAMEWORK_I18N_RESOURCES = [
|
|
3052
|
+
{ prefix: `/${ZAMBON_FRAMEWORK_I18N_ASSET_PATH}/`, suffix: '.json' },
|
|
3053
|
+
];
|
|
3054
|
+
function createZambonFrameworkTranslateLoader(httpBackend, appResources = []) {
|
|
3055
|
+
return new MultiTranslateHttpLoader(httpBackend, [...ZAMBON_FRAMEWORK_I18N_RESOURCES, ...appResources]);
|
|
3056
|
+
}
|
|
3057
|
+
function provideZambonFrameworkTranslateLoader(appResources = []) {
|
|
3058
|
+
return {
|
|
3059
|
+
provide: TranslateLoader,
|
|
3060
|
+
useFactory: (httpBackend) => createZambonFrameworkTranslateLoader(httpBackend, appResources),
|
|
3061
|
+
deps: [HttpBackend],
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
function provideZambonFrameworkTranslateService(config = {}) {
|
|
3065
|
+
return provideTranslateService({
|
|
3066
|
+
defaultLanguage: config.defaultLanguage,
|
|
3067
|
+
loader: provideZambonFrameworkTranslateLoader(config.appResources),
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3072
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3073
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3074
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3075
|
+
class ViewBase {
|
|
3076
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3077
|
+
//#endregion
|
|
3078
|
+
//#region Variables
|
|
3079
|
+
destroy$ = new Subject();
|
|
3080
|
+
// A signal rather than a field: a view's own template reads `loading` to show its spinner,
|
|
3081
|
+
// and every assignment here happens inside a subscription. The accessors below keep their
|
|
3082
|
+
// old shape, so a subclass reading or writing `this.loading` needs no change; a subclass
|
|
3083
|
+
// template is better off reading `loadingState()` directly.
|
|
3084
|
+
loadingState = signal(false, /* @ts-ignore */
|
|
3085
|
+
...(ngDevMode ? [{ debugName: "loadingState" }] : /* istanbul ignore next */ []));
|
|
3086
|
+
//#endregion
|
|
3087
|
+
//#region Properties
|
|
3088
|
+
get loading() {
|
|
3089
|
+
return this.loadingState();
|
|
3090
|
+
}
|
|
3091
|
+
set loading(value) {
|
|
3092
|
+
this.loadingState.set(value);
|
|
3093
|
+
}
|
|
3094
|
+
//#endregion
|
|
3095
|
+
//#region Constructor and Angular life cycle methods
|
|
3096
|
+
ngOnDestroy() {
|
|
3097
|
+
this.destroy$.next(true);
|
|
3098
|
+
this.destroy$.complete();
|
|
3099
|
+
}
|
|
3100
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ViewBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3101
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ViewBase, isStandalone: true, selector: "ng-component", ngImport: i0, template: '', isInline: true });
|
|
3102
|
+
}
|
|
3103
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ViewBase, decorators: [{
|
|
3104
|
+
type: Component,
|
|
3105
|
+
args: [{ template: '' }]
|
|
3106
|
+
}] });
|
|
3107
|
+
|
|
3108
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3109
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3110
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3111
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3112
|
+
class ChildList extends ViewBase {
|
|
3113
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3114
|
+
//#endregion
|
|
3115
|
+
//#region Variables
|
|
3116
|
+
dataGridDataset;
|
|
3117
|
+
dataProvider;
|
|
3118
|
+
resultDataset;
|
|
3119
|
+
selectionCount = 0;
|
|
3120
|
+
//#endregion
|
|
3121
|
+
//#region Properties
|
|
3122
|
+
get isLoading() {
|
|
3123
|
+
return this.loading;
|
|
3124
|
+
}
|
|
3125
|
+
//#endregion
|
|
3126
|
+
//#region Constructor and Angular life cycle methods
|
|
3127
|
+
constructor() {
|
|
3128
|
+
super();
|
|
3129
|
+
this.dataGridDataset = inject(DataGridDataset);
|
|
3130
|
+
this.dataProvider = inject(DataProviderService, { optional: true });
|
|
3131
|
+
this.resultDataset = inject(MultiSelectResultDataset, { optional: true });
|
|
3132
|
+
}
|
|
3133
|
+
ngOnInit() {
|
|
3134
|
+
this.dataGridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3135
|
+
this.loading = true;
|
|
3136
|
+
});
|
|
3137
|
+
this.dataGridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3138
|
+
this.loading = false;
|
|
3139
|
+
});
|
|
3140
|
+
this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3141
|
+
this.selectedRowChanged();
|
|
3142
|
+
});
|
|
3143
|
+
this.resultDataset?.savedChanges?.pipe(takeUntil(this.destroy$))?.subscribe(() => {
|
|
3144
|
+
this.dataGridDataset.refresh();
|
|
3145
|
+
});
|
|
3146
|
+
this.dataProvider
|
|
3147
|
+
?.getModel$()
|
|
3148
|
+
.pipe(skip(1), takeUntil(this.destroy$))
|
|
3149
|
+
.subscribe(() => {
|
|
3150
|
+
this.dataGridDataset.refresh();
|
|
3151
|
+
});
|
|
3152
|
+
if (this.dataProvider?.hasEntityID) {
|
|
3153
|
+
this.dataGridDataset.parentEntityId = this.dataProvider.entityID;
|
|
3154
|
+
if (!this.dataGridDataset.loadedRows) {
|
|
3155
|
+
this.dataGridDataset.refresh();
|
|
3156
|
+
}
|
|
3157
|
+
if (this.resultDataset) {
|
|
3158
|
+
this.resultDataset.parentEntityId = this.dataProvider.entityID;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
//#endregion
|
|
3163
|
+
//#region Event handlers
|
|
3164
|
+
//#endregion
|
|
3165
|
+
//#region Public methods
|
|
3166
|
+
//#endregion
|
|
3167
|
+
//#region Private methods
|
|
3168
|
+
selectedRowChanged() {
|
|
3169
|
+
this.selectionCount = this.dataGridDataset.selectedRowKeys.length;
|
|
3170
|
+
}
|
|
3171
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ChildList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3172
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ChildList, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3173
|
+
}
|
|
3174
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ChildList, decorators: [{
|
|
3175
|
+
type: Component,
|
|
3176
|
+
args: [{ template: '' }]
|
|
3177
|
+
}], ctorParameters: () => [] });
|
|
3178
|
+
|
|
3179
|
+
/**
|
|
3180
|
+
* @deprecated Use standalone {@link ChildList} instead. Migrate by extending ChildList
|
|
3181
|
+
* and using standalone component imports with inject() for dependency injection.
|
|
3182
|
+
*/
|
|
3183
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3184
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3185
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3186
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3187
|
+
class ChildListLegacy extends ViewBase {
|
|
3188
|
+
_entityId;
|
|
3189
|
+
selectionCount = 0;
|
|
3190
|
+
get entityId() {
|
|
3191
|
+
return this._entityId;
|
|
3192
|
+
}
|
|
3193
|
+
set entityId(value) {
|
|
3194
|
+
if (this._entityId !== value) {
|
|
3195
|
+
this._entityId = value;
|
|
3196
|
+
this.dataGridDataset.parentEntityId = value;
|
|
3197
|
+
if (!this.dataGridDataset.loadedRows) {
|
|
3198
|
+
this.dataGridDataset.refresh();
|
|
3199
|
+
}
|
|
3200
|
+
if (this.resultDataset) {
|
|
3201
|
+
this.resultDataset.parentEntityId = value;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
disabled = false;
|
|
3206
|
+
dataGridDataset = inject(DataGridDataset);
|
|
3207
|
+
resultDataset = inject(MultiSelectResultDataset, { optional: true });
|
|
3208
|
+
constructor() {
|
|
3209
|
+
super();
|
|
3210
|
+
}
|
|
3211
|
+
ngOnInit() {
|
|
3212
|
+
this.dataGridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3213
|
+
this.loading = true;
|
|
3214
|
+
});
|
|
3215
|
+
this.dataGridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3216
|
+
this.loading = false;
|
|
3217
|
+
});
|
|
3218
|
+
if (!this.dataGridDataset.loadedRows) {
|
|
3219
|
+
this.loading = true;
|
|
3220
|
+
}
|
|
3221
|
+
this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3222
|
+
this.selectedRowChanged();
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
selectedRowChanged() {
|
|
3226
|
+
this.selectionCount = this.dataGridDataset.selectedRowKeys.length;
|
|
3227
|
+
}
|
|
3228
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ChildListLegacy, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3229
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ChildListLegacy, isStandalone: true, selector: "ng-component", inputs: { entityId: "entityId", disabled: "disabled" }, usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3230
|
+
}
|
|
3231
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ChildListLegacy, decorators: [{
|
|
3232
|
+
type: Component,
|
|
3233
|
+
args: [{ template: '' }]
|
|
3234
|
+
}], ctorParameters: () => [], propDecorators: { entityId: [{
|
|
3235
|
+
type: Input
|
|
3236
|
+
}], disabled: [{
|
|
3237
|
+
type: Input
|
|
3238
|
+
}] } });
|
|
3239
|
+
|
|
3240
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3241
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3242
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3243
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3244
|
+
class TabViewBase extends ViewBase {
|
|
3245
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3246
|
+
ribbonTemplate;
|
|
3247
|
+
//#endregion
|
|
3248
|
+
//#region Variables
|
|
3249
|
+
tabViewService;
|
|
3250
|
+
//#endregion
|
|
3251
|
+
//#region Properties
|
|
3252
|
+
//#endregion
|
|
3253
|
+
//#region Constructor and Angular life cycle methods
|
|
3254
|
+
constructor() {
|
|
3255
|
+
super();
|
|
3256
|
+
// This will force the screen to initialize with the buttons disabled.
|
|
3257
|
+
this.loading = true;
|
|
3258
|
+
this.tabViewService = inject(TabViewService, { optional: true });
|
|
3259
|
+
}
|
|
3260
|
+
ngAfterViewInit() {
|
|
3261
|
+
this.tabViewService?.updateRibbonTemplate(this.ribbonTemplate);
|
|
3262
|
+
}
|
|
3263
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3264
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: TabViewBase, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "ribbonTemplate", first: true, predicate: ["ribbon"], descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3265
|
+
}
|
|
3266
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewBase, decorators: [{
|
|
3267
|
+
type: Component,
|
|
3268
|
+
args: [{ template: '' }]
|
|
3269
|
+
}], ctorParameters: () => [], propDecorators: { ribbonTemplate: [{
|
|
3270
|
+
type: ViewChild,
|
|
3271
|
+
args: ['ribbon']
|
|
3272
|
+
}] } });
|
|
3273
|
+
|
|
3274
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3275
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3276
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3277
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3278
|
+
class FormView extends TabViewBase {
|
|
3279
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3280
|
+
//#endregion
|
|
3281
|
+
//#region Variables
|
|
3282
|
+
dataForm;
|
|
3283
|
+
dataProvider;
|
|
3284
|
+
formBuilder;
|
|
3285
|
+
formService;
|
|
3286
|
+
//#endregion
|
|
3287
|
+
//#region Properties
|
|
3288
|
+
get hasEntityID() {
|
|
3289
|
+
return this.dataProvider?.hasEntityID ?? false;
|
|
3290
|
+
}
|
|
3291
|
+
get isLoading() {
|
|
3292
|
+
return this.formService.loading;
|
|
3293
|
+
}
|
|
3294
|
+
//#endregion
|
|
3295
|
+
//#region Constructor and Angular life cycle methods
|
|
3296
|
+
constructor() {
|
|
3297
|
+
super();
|
|
3298
|
+
this.dataProvider = inject(DataProviderService, { optional: true });
|
|
3299
|
+
this.formBuilder = inject(FormBuilder);
|
|
3300
|
+
this.formService = inject(FormService);
|
|
3301
|
+
}
|
|
3302
|
+
ngOnInit() {
|
|
3303
|
+
this.dataForm = this.formSetup();
|
|
3304
|
+
this.formService.initializeForm(this.dataForm);
|
|
3305
|
+
this.formService.loading = true;
|
|
3306
|
+
this.dataProvider
|
|
3307
|
+
?.getModel$()
|
|
3308
|
+
.pipe(takeUntil(this.destroy$))
|
|
3309
|
+
.subscribe((model) => {
|
|
3310
|
+
this.formService.loading = false;
|
|
3311
|
+
this.formService.model = model;
|
|
3312
|
+
this.formService.resetForm();
|
|
3313
|
+
if (!this.dataProvider?.hasEntityID) {
|
|
3314
|
+
this.formService.beginEdit();
|
|
3315
|
+
}
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FormView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3319
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: FormView, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3320
|
+
}
|
|
3321
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FormView, decorators: [{
|
|
3322
|
+
type: Component,
|
|
3323
|
+
args: [{ template: '' }]
|
|
3324
|
+
}], ctorParameters: () => [] });
|
|
3325
|
+
|
|
3326
|
+
/**
|
|
3327
|
+
* @deprecated Use standalone {@link FormView} instead. Migrate by extending FormView
|
|
3328
|
+
* and using standalone component imports with inject() for dependency injection.
|
|
3329
|
+
*/
|
|
3330
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3331
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3332
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3333
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3334
|
+
class LegacySubViewForm extends ViewBase {
|
|
3335
|
+
formBuilder;
|
|
3336
|
+
formService;
|
|
3337
|
+
entityId;
|
|
3338
|
+
set model(value) {
|
|
3339
|
+
if (this._model !== value) {
|
|
3340
|
+
this._model = value;
|
|
3341
|
+
if (this.dataForm) {
|
|
3342
|
+
this.dataForm.patchValue(this.model);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
get loading() {
|
|
3347
|
+
return false;
|
|
3348
|
+
return this.formService.loading;
|
|
3349
|
+
}
|
|
3350
|
+
set loading(value) {
|
|
3351
|
+
this.formService.loading = value;
|
|
3352
|
+
}
|
|
3353
|
+
get isNew() {
|
|
3354
|
+
return !this.entityId;
|
|
3355
|
+
}
|
|
3356
|
+
// A signal rather than a field: the legacy edit and save buttons decide what to render from
|
|
3357
|
+
// `form.viewMode`, and this flips inside beginEdit/cancelEdit with nothing marking their views
|
|
3358
|
+
// dirty. The accessors keep the old shape.
|
|
3359
|
+
get viewMode() {
|
|
3360
|
+
return this._viewMode();
|
|
3361
|
+
}
|
|
3362
|
+
set viewMode(value) {
|
|
3363
|
+
this._viewMode.set(value);
|
|
3364
|
+
}
|
|
3365
|
+
get model() {
|
|
3366
|
+
return this._model;
|
|
3367
|
+
}
|
|
3368
|
+
dataForm;
|
|
3369
|
+
_model = null;
|
|
3370
|
+
_viewMode = signal('view', /* @ts-ignore */
|
|
3371
|
+
...(ngDevMode ? [{ debugName: "_viewMode" }] : /* istanbul ignore next */ []));
|
|
3372
|
+
dataProvider;
|
|
3373
|
+
constructor(formBuilder, formService) {
|
|
3374
|
+
super();
|
|
3375
|
+
this.formBuilder = formBuilder;
|
|
3376
|
+
this.formService = formService;
|
|
3377
|
+
this.dataProvider = inject(DataProviderService, { optional: true });
|
|
3378
|
+
}
|
|
3379
|
+
ngOnInit() {
|
|
3380
|
+
this.dataForm = this.formSetup();
|
|
3381
|
+
this.updateModel(this.model);
|
|
3382
|
+
if (this.dataProvider) {
|
|
3383
|
+
this.formService.loading = true;
|
|
3384
|
+
this.dataProvider
|
|
3385
|
+
.getModel$()
|
|
3386
|
+
.pipe(take(1))
|
|
3387
|
+
.subscribe((_) => {
|
|
3388
|
+
this.formService.loading = false;
|
|
3389
|
+
});
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
updateModel(model) {
|
|
3393
|
+
this._model = model;
|
|
3394
|
+
if (this.isNew) {
|
|
3395
|
+
this.beginEdit();
|
|
3396
|
+
}
|
|
3397
|
+
else {
|
|
3398
|
+
this.cancelEdit();
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
beginEdit(resetValues = true) {
|
|
3402
|
+
setTimeout(() => {
|
|
3403
|
+
this.viewMode = 'edit';
|
|
3404
|
+
this.dataForm.enable();
|
|
3405
|
+
if (resetValues) {
|
|
3406
|
+
this.dataForm.patchValue(this.model);
|
|
3407
|
+
}
|
|
3408
|
+
});
|
|
3409
|
+
}
|
|
3410
|
+
cancelEdit() {
|
|
3411
|
+
this.viewMode = 'view';
|
|
3412
|
+
this.dataForm.patchValue(this.model);
|
|
3413
|
+
this.dataForm.disable();
|
|
3414
|
+
}
|
|
3415
|
+
/** Extension point: subclasses override this to validate before save. */
|
|
3416
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
|
3417
|
+
validate(_formModel) { }
|
|
3418
|
+
save() {
|
|
3419
|
+
this.validate(this.dataForm.getRawValue());
|
|
3420
|
+
if (this.dataForm.valid) {
|
|
3421
|
+
this.dataForm.disable();
|
|
3422
|
+
return this.saveModel().pipe(tap(() => this.dataForm.enable()));
|
|
3423
|
+
}
|
|
3424
|
+
else {
|
|
3425
|
+
this.dataForm.markAllAsTouched();
|
|
3426
|
+
return throwError(() => new HttpErrorResponse({ error: { message: 'Form invalid', errors: null }, status: 400 }));
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacySubViewForm, deps: [{ token: i1$2.FormBuilder }, { token: i1$3.FormService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3430
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: LegacySubViewForm, isStandalone: true, selector: "ng-component", inputs: { entityId: "entityId", model: "model" }, usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3431
|
+
}
|
|
3432
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacySubViewForm, decorators: [{
|
|
3433
|
+
type: Component,
|
|
3434
|
+
args: [{ template: '' }]
|
|
3435
|
+
}], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: i1$3.FormService }], propDecorators: { entityId: [{
|
|
3436
|
+
type: Input
|
|
3437
|
+
}], model: [{
|
|
3438
|
+
type: Input
|
|
3439
|
+
}] } });
|
|
3440
|
+
|
|
3441
|
+
/**
|
|
3442
|
+
* @deprecated Use standalone {@link TabViewBase} instead. Migrate by extending TabViewBase
|
|
3443
|
+
* and using standalone component imports with inject() for dependency injection.
|
|
3444
|
+
*/
|
|
3445
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3446
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3447
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3448
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3449
|
+
class LegacyTabViewBase extends ViewBase {
|
|
3450
|
+
route;
|
|
3451
|
+
tabService;
|
|
3452
|
+
title = '';
|
|
3453
|
+
url;
|
|
3454
|
+
activeView;
|
|
3455
|
+
constructor(route, tabService) {
|
|
3456
|
+
super();
|
|
3457
|
+
this.route = route;
|
|
3458
|
+
this.tabService = tabService;
|
|
3459
|
+
this.url = RouterFormatter.getURL(this.route.snapshot);
|
|
3460
|
+
}
|
|
3461
|
+
ngOnInit() {
|
|
3462
|
+
this.openTabView();
|
|
3463
|
+
}
|
|
3464
|
+
openTabView() {
|
|
3465
|
+
// this.tabService.openView(this);
|
|
3466
|
+
}
|
|
3467
|
+
updateTitle(title) {
|
|
3468
|
+
this.title = title;
|
|
3469
|
+
this.tabService.updateTabTitle(this.url, title);
|
|
3470
|
+
}
|
|
3471
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewBase, deps: [{ token: i1$1.ActivatedRoute }, { token: TabService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3472
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: LegacyTabViewBase, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3473
|
+
}
|
|
3474
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewBase, decorators: [{
|
|
3475
|
+
type: Component,
|
|
3476
|
+
args: [{ template: '' }]
|
|
3477
|
+
}], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: TabService }] });
|
|
3478
|
+
|
|
3479
|
+
/**
|
|
3480
|
+
* @deprecated Use standalone {@link TabViewBase} with {@link DefaultDetailsTabViewComponent} pattern instead.
|
|
3481
|
+
* Migrate by extending TabViewBase and composing detail/history views as child routes
|
|
3482
|
+
* with standalone component imports.
|
|
3483
|
+
*/
|
|
3484
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3485
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3486
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3487
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3488
|
+
class LegacyTabViewDetails extends LegacyTabViewBase {
|
|
3489
|
+
formService;
|
|
3490
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3491
|
+
// A forward reference: the legacy buttons import the views barrel, so this class is defined before
|
|
3492
|
+
// ButtonNewLegacyComponent -- and in the published bundle it sits further down the file, where
|
|
3493
|
+
// reading it eagerly throws the moment a JIT runtime such as Jest loads the package.
|
|
3494
|
+
buttonNew;
|
|
3495
|
+
//#endregion
|
|
3496
|
+
//#region Variables
|
|
3497
|
+
title = '';
|
|
3498
|
+
defaultView = 'details';
|
|
3499
|
+
entityId;
|
|
3500
|
+
views = [
|
|
3501
|
+
{ id: 'details', label: 'Button-Views-Details', icon: 'fa-dice-d6' },
|
|
3502
|
+
{ id: 'history', label: 'Button-Views-History', icon: 'fa-history' },
|
|
3503
|
+
];
|
|
3504
|
+
//#endregion
|
|
3505
|
+
//#region Properties
|
|
3506
|
+
get loading() {
|
|
3507
|
+
return super.loading;
|
|
3508
|
+
}
|
|
3509
|
+
set loading(value) {
|
|
3510
|
+
super.loading = value;
|
|
3511
|
+
this.formService.loading = value;
|
|
3512
|
+
}
|
|
3513
|
+
get currentView() {
|
|
3514
|
+
return this.activeView ?? this.defaultView;
|
|
3515
|
+
}
|
|
3516
|
+
get model() {
|
|
3517
|
+
return this.formService.model;
|
|
3518
|
+
}
|
|
3519
|
+
//#endregion
|
|
3520
|
+
//#region Constructor and Angular life cycle methods
|
|
3521
|
+
constructor(route, tabService, formService) {
|
|
3522
|
+
super(route, tabService);
|
|
3523
|
+
this.formService = formService;
|
|
3524
|
+
}
|
|
3525
|
+
ngAfterViewInit() {
|
|
3526
|
+
this.onViewVisible();
|
|
3527
|
+
}
|
|
3528
|
+
ngOnInit() {
|
|
3529
|
+
this.refreshParameters();
|
|
3530
|
+
super.ngOnInit();
|
|
3531
|
+
this.formService.modelRefreshed.pipe(takeUntil(this.destroy$)).subscribe((_) => {
|
|
3532
|
+
const id = this.getModelId();
|
|
3533
|
+
if (id && this.entityId !== id) {
|
|
3534
|
+
this.entityId = id;
|
|
3535
|
+
this.tabService.redirectCurrentTab(`${this.url.substring(0, this.url.indexOf('/new'))}/${id}`);
|
|
3536
|
+
}
|
|
3537
|
+
this.updateTitle(this.getModelDescription());
|
|
3538
|
+
});
|
|
3539
|
+
this.refresh();
|
|
3540
|
+
}
|
|
3541
|
+
//#endregion
|
|
3542
|
+
//#region Event handlers
|
|
3543
|
+
//#endregion
|
|
3544
|
+
//#region Public methods
|
|
3545
|
+
openTabView() {
|
|
3546
|
+
super.openTabView();
|
|
3547
|
+
const activeView = this.views.find((view) => view.id === this.activeView);
|
|
3548
|
+
if (activeView) {
|
|
3549
|
+
activeView.isDisabled = true;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
refresh() {
|
|
3553
|
+
this.loading = true;
|
|
3554
|
+
this.title = '';
|
|
3555
|
+
this.formService.model = null;
|
|
3556
|
+
this.loadModel()
|
|
3557
|
+
.pipe(takeUntil(this.destroy$))
|
|
3558
|
+
.subscribe((model) => {
|
|
3559
|
+
this.loading = false;
|
|
3560
|
+
this.formService.model = model;
|
|
3561
|
+
});
|
|
3562
|
+
}
|
|
3563
|
+
refreshParameters() {
|
|
3564
|
+
const currentID = this.route.snapshot.paramMap.get('id');
|
|
3565
|
+
this.entityId = currentID ? Number(currentID) : undefined;
|
|
3566
|
+
const viewID = this.route.snapshot.paramMap.get('view');
|
|
3567
|
+
if (!viewID || !this.views.some((view) => view.id === viewID)) {
|
|
3568
|
+
this.switchView(this.defaultView);
|
|
3569
|
+
}
|
|
3570
|
+
else {
|
|
3571
|
+
this.activeView = viewID;
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
switchView(viewId) {
|
|
3575
|
+
if (!viewId) {
|
|
3576
|
+
viewId = this.defaultView;
|
|
3577
|
+
}
|
|
3578
|
+
const activeView = this.views.find((view) => view.id === (this.activeView ?? this.defaultView));
|
|
3579
|
+
if (activeView) {
|
|
3580
|
+
activeView.isDisabled = false;
|
|
3581
|
+
}
|
|
3582
|
+
const newView = this.views.find((view) => view.id === viewId);
|
|
3583
|
+
if (newView) {
|
|
3584
|
+
newView.isDisabled = true;
|
|
3585
|
+
const urlViewPath = viewId === this.defaultView ? '' : `/${viewId}`;
|
|
3586
|
+
let url = this.url;
|
|
3587
|
+
if (this.activeView && url.endsWith(this.activeView)) {
|
|
3588
|
+
url = url.replace(`/${this.activeView}`, urlViewPath);
|
|
3589
|
+
}
|
|
3590
|
+
else {
|
|
3591
|
+
url += urlViewPath;
|
|
3592
|
+
}
|
|
3593
|
+
this.tabService.navigateCurrentTab(new Tab({ url }));
|
|
3594
|
+
// this.tabService.changeTabView(this, viewId !== this.defaultView ? viewId : undefined);
|
|
3595
|
+
this.activeView = newView.id;
|
|
3596
|
+
// if (!this.activeView) {
|
|
3597
|
+
// this.activeView = this.defaultView;
|
|
3598
|
+
// }
|
|
3599
|
+
}
|
|
3600
|
+
this.onViewVisible();
|
|
3601
|
+
}
|
|
3602
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewDetails, deps: [{ token: i1$1.ActivatedRoute }, { token: TabService }, { token: i1$3.FormService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3603
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: LegacyTabViewDetails, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "buttonNew", first: true, predicate: i0.forwardRef(() => ButtonNewLegacyComponent), descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3604
|
+
}
|
|
3605
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewDetails, decorators: [{
|
|
3606
|
+
type: Component,
|
|
3607
|
+
args: [{ template: '' }]
|
|
3608
|
+
}], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: TabService }, { type: i1$3.FormService }], propDecorators: { buttonNew: [{
|
|
3609
|
+
type: ViewChild,
|
|
3610
|
+
args: [forwardRef(() => ButtonNewLegacyComponent)]
|
|
3611
|
+
}] } });
|
|
3612
|
+
|
|
3613
|
+
/**
|
|
3614
|
+
* @deprecated Use standalone {@link TabViewList} instead. Migrate by extending TabViewList
|
|
3615
|
+
* and using standalone component imports with inject() for dependency injection.
|
|
3616
|
+
*/
|
|
3617
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3618
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3619
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3620
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3621
|
+
class LegacyTabViewList extends LegacyTabViewBase {
|
|
3622
|
+
dataGridDataset;
|
|
3623
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3624
|
+
//#endregion
|
|
3625
|
+
//#region Variables
|
|
3626
|
+
_selectedItem;
|
|
3627
|
+
selectedRows = 0;
|
|
3628
|
+
//#endregion
|
|
3629
|
+
//#region Properties
|
|
3630
|
+
get hasRowsSelected() {
|
|
3631
|
+
return this.selectedRows > 0;
|
|
3632
|
+
}
|
|
3633
|
+
get selectedItem() {
|
|
3634
|
+
return this._selectedItem;
|
|
3635
|
+
}
|
|
3636
|
+
//#endregion
|
|
3637
|
+
//#region Constructor and Angular life cycle methods
|
|
3638
|
+
constructor(dataGridDataset, route, tabService) {
|
|
3639
|
+
super(route, tabService);
|
|
3640
|
+
this.dataGridDataset = dataGridDataset;
|
|
3641
|
+
// This will force the screen to initialize with the buttons disabled.
|
|
3642
|
+
this.loading = true;
|
|
3643
|
+
}
|
|
3644
|
+
ngOnInit() {
|
|
3645
|
+
super.ngOnInit();
|
|
3646
|
+
this.dataGridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3647
|
+
this.loading = true;
|
|
3648
|
+
});
|
|
3649
|
+
this.dataGridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3650
|
+
setTimeout(() => (this.loading = false));
|
|
3651
|
+
});
|
|
3652
|
+
this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3653
|
+
this.selectedRowChanged();
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
//#endregion
|
|
3657
|
+
//#region Event handlers
|
|
3658
|
+
//#endregion
|
|
3659
|
+
//#region Public methods
|
|
3660
|
+
//#endregion
|
|
3661
|
+
//#region Private methods
|
|
3662
|
+
selectedRowChanged() {
|
|
3663
|
+
this.selectedRows = this.dataGridDataset.selectedRowKeys.length;
|
|
3664
|
+
this._selectedItem = undefined;
|
|
3665
|
+
if (this.selectedRows === 1) {
|
|
3666
|
+
this._selectedItem = this.dataGridDataset.getRowData(this.dataGridDataset.selectedRowKeys[0]);
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewList, deps: [{ token: i1$3.DataGridDataset }, { token: i1$1.ActivatedRoute }, { token: TabService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3670
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: LegacyTabViewList, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3671
|
+
}
|
|
3672
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewList, decorators: [{
|
|
3673
|
+
type: Component,
|
|
3674
|
+
args: [{ template: '' }]
|
|
3675
|
+
}], ctorParameters: () => [{ type: i1$3.DataGridDataset }, { type: i1$1.ActivatedRoute }, { type: TabService }] });
|
|
3676
|
+
|
|
3677
|
+
// The host class is inherited by every subclass (Angular merges hostAttrs from a base component
|
|
3678
|
+
// definition), which is what lets DefaultTabViewComponent give routed list screens a full-height
|
|
3679
|
+
// layout without each one declaring its own stylesheet. Plain class name, NOT Tailwind utilities:
|
|
3680
|
+
// library SCSS resolves @apply at library-build time, whereas a utility class named here would
|
|
3681
|
+
// have to be generated by each consumer's own Tailwind build.
|
|
3682
|
+
//
|
|
3683
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3684
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3685
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3686
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3687
|
+
class TabViewList extends TabViewBase {
|
|
3688
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3689
|
+
//#endregion
|
|
3690
|
+
//#region Variables
|
|
3691
|
+
dataGridDataset;
|
|
3692
|
+
_selectedItem;
|
|
3693
|
+
selectionCount = 0;
|
|
3694
|
+
//#endregion
|
|
3695
|
+
//#region Properties
|
|
3696
|
+
get hasRowsSelected() {
|
|
3697
|
+
return this.selectionCount > 0;
|
|
3698
|
+
}
|
|
3699
|
+
get selectedItem() {
|
|
3700
|
+
return this._selectedItem;
|
|
3701
|
+
}
|
|
3702
|
+
//#endregion
|
|
3703
|
+
//#region Constructor and Angular life cycle methods
|
|
3704
|
+
constructor() {
|
|
3705
|
+
super();
|
|
3706
|
+
this.dataGridDataset = inject(DataGridDataset);
|
|
3707
|
+
}
|
|
3708
|
+
ngOnInit() {
|
|
3709
|
+
this.dataGridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3710
|
+
this.loading = true;
|
|
3711
|
+
});
|
|
3712
|
+
this.dataGridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3713
|
+
setTimeout(() => (this.loading = false));
|
|
3714
|
+
});
|
|
3715
|
+
this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3716
|
+
this.selectedRowChanged();
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
//#endregion
|
|
3720
|
+
//#region Event handlers
|
|
3721
|
+
//#endregion
|
|
3722
|
+
//#region Public methods
|
|
3723
|
+
//#endregion
|
|
3724
|
+
//#region Private methods
|
|
3725
|
+
selectedRowChanged() {
|
|
3726
|
+
this.selectionCount = this.dataGridDataset.selectedRowKeys.length;
|
|
3727
|
+
this._selectedItem = undefined;
|
|
3728
|
+
if (this.selectionCount === 1) {
|
|
3729
|
+
this._selectedItem = this.dataGridDataset.getRowData(this.dataGridDataset.selectedRowKeys[0]);
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3733
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: TabViewList, isStandalone: true, selector: "ng-component", host: { classAttribute: "framework-view-list" }, usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3734
|
+
}
|
|
3735
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewList, decorators: [{
|
|
3736
|
+
type: Component,
|
|
3737
|
+
args: [{ host: { class: 'framework-view-list' }, template: '' }]
|
|
3738
|
+
}], ctorParameters: () => [] });
|
|
3739
|
+
|
|
3740
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3741
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3742
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3743
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3744
|
+
class ListView extends TabViewList {
|
|
3745
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3746
|
+
//#endregion
|
|
3747
|
+
//#region Variables
|
|
3748
|
+
dataProvider;
|
|
3749
|
+
resultDataset;
|
|
3750
|
+
//#endregion
|
|
3751
|
+
//#region Properties
|
|
3752
|
+
//#endregion
|
|
3753
|
+
//#region Constructor and Angular life cycle methods
|
|
3754
|
+
constructor() {
|
|
3755
|
+
super();
|
|
3756
|
+
this.dataProvider = inject(DataProviderService, { optional: true });
|
|
3757
|
+
this.resultDataset = inject(MultiSelectResultDataset, { optional: true });
|
|
3758
|
+
}
|
|
3759
|
+
ngOnInit() {
|
|
3760
|
+
super.ngOnInit();
|
|
3761
|
+
if (this.dataProvider?.hasEntityID) {
|
|
3762
|
+
this.dataGridDataset.parentEntityId = this.dataProvider.entityID;
|
|
3763
|
+
if (!this.dataGridDataset.loadedRows) {
|
|
3764
|
+
this.dataGridDataset.refresh();
|
|
3765
|
+
}
|
|
3766
|
+
if (this.resultDataset) {
|
|
3767
|
+
this.resultDataset.parentEntityId = this.dataProvider.entityID;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ListView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3772
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ListView, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3773
|
+
}
|
|
3774
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ListView, decorators: [{
|
|
3775
|
+
type: Component,
|
|
3776
|
+
args: [{ template: '' }]
|
|
3777
|
+
}], ctorParameters: () => [] });
|
|
3778
|
+
|
|
3779
|
+
class ModalBase extends ViewBase {
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3783
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3784
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3785
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3786
|
+
class MultiEditorLegacyModal extends ModalBase {
|
|
3787
|
+
dataGridDataset;
|
|
3788
|
+
multiEditorDataset;
|
|
3789
|
+
formBuilder;
|
|
3790
|
+
_entityId;
|
|
3791
|
+
multiEditor;
|
|
3792
|
+
set entityId(value) {
|
|
3793
|
+
if (this._entityId !== value) {
|
|
3794
|
+
this._entityId = value;
|
|
3795
|
+
this.dataGridDataset.parentEntityId = value;
|
|
3796
|
+
this.multiEditorDataset.parentEntityId = value;
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
savedChanges = new EventEmitter();
|
|
3800
|
+
form;
|
|
3801
|
+
constructor(dataGridDataset, multiEditorDataset, formBuilder) {
|
|
3802
|
+
super();
|
|
3803
|
+
this.dataGridDataset = dataGridDataset;
|
|
3804
|
+
this.multiEditorDataset = multiEditorDataset;
|
|
3805
|
+
this.formBuilder = formBuilder;
|
|
3806
|
+
}
|
|
3807
|
+
ngOnInit() {
|
|
3808
|
+
this.form = this.formSetup();
|
|
3809
|
+
this.multiEditorDataset.savedChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3810
|
+
this.savedChanges.emit();
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
toggle() {
|
|
3814
|
+
this.multiEditor.toggleModal();
|
|
3815
|
+
}
|
|
3816
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiEditorLegacyModal, deps: [{ token: i1$3.DataGridDataset }, { token: i1$3.MultiEditorDataset }, { token: i1$2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
|
|
3817
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MultiEditorLegacyModal, isStandalone: true, selector: "ng-component", inputs: { entityId: "entityId" }, outputs: { savedChanges: "savedChanges" }, viewQueries: [{ propertyName: "multiEditor", first: true, predicate: MultiEditorComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3818
|
+
}
|
|
3819
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiEditorLegacyModal, decorators: [{
|
|
3820
|
+
type: Component,
|
|
3821
|
+
args: [{ template: '' }]
|
|
3822
|
+
}], ctorParameters: () => [{ type: i1$3.DataGridDataset }, { type: i1$3.MultiEditorDataset }, { type: i1$2.FormBuilder }], propDecorators: { multiEditor: [{
|
|
3823
|
+
type: ViewChild,
|
|
3824
|
+
args: [MultiEditorComponent]
|
|
3825
|
+
}], entityId: [{
|
|
3826
|
+
type: Input
|
|
3827
|
+
}], savedChanges: [{
|
|
3828
|
+
type: Output
|
|
3829
|
+
}] } });
|
|
3830
|
+
|
|
3831
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3832
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3833
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3834
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3835
|
+
class MultiEditorModal extends ModalBase {
|
|
3836
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3837
|
+
multiEditor;
|
|
3838
|
+
overrideParentID;
|
|
3839
|
+
savedChanges = new EventEmitter();
|
|
3840
|
+
//#endregion
|
|
3841
|
+
//#region Variables
|
|
3842
|
+
dataForm;
|
|
3843
|
+
dataGridDataset;
|
|
3844
|
+
dataProvider;
|
|
3845
|
+
formBuilder;
|
|
3846
|
+
formService;
|
|
3847
|
+
multiEditorDataset;
|
|
3848
|
+
selectionCount = 0;
|
|
3849
|
+
//#endregion
|
|
3850
|
+
//#region Properties
|
|
3851
|
+
//#endregion
|
|
3852
|
+
//#region Constructor and Angular life cycle methods
|
|
3853
|
+
constructor() {
|
|
3854
|
+
super();
|
|
3855
|
+
this.dataGridDataset = inject(DataGridDataset);
|
|
3856
|
+
this.dataProvider = inject(DataProviderService, { optional: true });
|
|
3857
|
+
this.formBuilder = inject(FormBuilder);
|
|
3858
|
+
this.formService = inject(FormService);
|
|
3859
|
+
this.multiEditorDataset = inject(MultiEditorDataset);
|
|
3860
|
+
}
|
|
3861
|
+
ngOnInit() {
|
|
3862
|
+
this.dataForm = this.formSetup();
|
|
3863
|
+
this.formService.initializeForm(this.dataForm);
|
|
3864
|
+
this.multiEditorDataset.savedChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
3865
|
+
this.savedChanges.emit();
|
|
3866
|
+
});
|
|
3867
|
+
this.updateParentID();
|
|
3868
|
+
}
|
|
3869
|
+
//#endregion
|
|
3870
|
+
//#region Event handlers
|
|
3871
|
+
//#endregion
|
|
3872
|
+
//#region Public methods
|
|
3873
|
+
toggle() {
|
|
3874
|
+
if (!this.multiEditor.isOpen()) {
|
|
3875
|
+
this.updateParentID();
|
|
3876
|
+
}
|
|
3877
|
+
this.multiEditor.toggleModal();
|
|
3878
|
+
}
|
|
3879
|
+
updateParentID() {
|
|
3880
|
+
let parentID = this.dataProvider?.entityID;
|
|
3881
|
+
if (this.overrideParentID) {
|
|
3882
|
+
parentID = this.overrideParentID;
|
|
3883
|
+
}
|
|
3884
|
+
if (parentID) {
|
|
3885
|
+
this.dataGridDataset.parentEntityId = parentID;
|
|
3886
|
+
this.multiEditorDataset.parentEntityId = parentID;
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiEditorModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3890
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MultiEditorModal, isStandalone: true, selector: "ng-component", inputs: { overrideParentID: "overrideParentID" }, outputs: { savedChanges: "savedChanges" }, viewQueries: [{ propertyName: "multiEditor", first: true, predicate: MultiEditorComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3891
|
+
}
|
|
3892
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiEditorModal, decorators: [{
|
|
3893
|
+
type: Component,
|
|
3894
|
+
args: [{ template: '' }]
|
|
3895
|
+
}], ctorParameters: () => [], propDecorators: { multiEditor: [{
|
|
3896
|
+
type: ViewChild,
|
|
3897
|
+
args: [MultiEditorComponent]
|
|
3898
|
+
}], overrideParentID: [{
|
|
3899
|
+
type: Input
|
|
3900
|
+
}], savedChanges: [{
|
|
3901
|
+
type: Output
|
|
3902
|
+
}] } });
|
|
3903
|
+
|
|
3904
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3905
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3906
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3907
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3908
|
+
class MultiSelectModal extends ModalBase {
|
|
3909
|
+
multiSelect;
|
|
3910
|
+
savedChanges = new EventEmitter();
|
|
3911
|
+
toggle() {
|
|
3912
|
+
this.multiSelect.toggleModal();
|
|
3913
|
+
}
|
|
3914
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiSelectModal, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
3915
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MultiSelectModal, isStandalone: true, selector: "ng-component", outputs: { savedChanges: "savedChanges" }, viewQueries: [{ propertyName: "multiSelect", first: true, predicate: MultiSelectComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3916
|
+
}
|
|
3917
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiSelectModal, decorators: [{
|
|
3918
|
+
type: Component,
|
|
3919
|
+
args: [{ template: '' }]
|
|
3920
|
+
}], propDecorators: { multiSelect: [{
|
|
3921
|
+
type: ViewChild,
|
|
3922
|
+
args: [MultiSelectComponent]
|
|
3923
|
+
}], savedChanges: [{
|
|
3924
|
+
type: Output
|
|
3925
|
+
}] } });
|
|
3926
|
+
|
|
3927
|
+
// No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
|
|
3928
|
+
// describe a class that is never instantiated, while every subclass compiles to whatever it
|
|
3929
|
+
// declares -- which in v22 means OnPush. Correctness comes from the state below notifying
|
|
3930
|
+
// Angular, never from this decorator. See docs/guides/reactivity.md.
|
|
3931
|
+
class ViewModal extends ModalBase {
|
|
3932
|
+
//#region ViewChilds, Inputs, Outputs
|
|
3933
|
+
modalComponent;
|
|
3934
|
+
//#endregion
|
|
3935
|
+
//#region Variables
|
|
3936
|
+
beginEditOnEntityLoad = false;
|
|
3937
|
+
data$;
|
|
3938
|
+
dataForm;
|
|
3939
|
+
formBuilder;
|
|
3940
|
+
formService;
|
|
3941
|
+
selectionCount = 0;
|
|
3942
|
+
entityIdSubject = new Subject();
|
|
3943
|
+
//#endregion
|
|
3944
|
+
//#region Properties
|
|
3945
|
+
//#endregion
|
|
3946
|
+
//#region Constructor and Angular life cycle methods
|
|
3947
|
+
constructor() {
|
|
3948
|
+
super();
|
|
3949
|
+
this.formBuilder = inject(FormBuilder);
|
|
3950
|
+
this.formService = inject(FormService);
|
|
3951
|
+
this.data$ = this.entityIdSubject.pipe(switchMap((entityID) => this.loadData(entityID)));
|
|
3952
|
+
}
|
|
3953
|
+
ngOnInit() {
|
|
3954
|
+
this.dataForm = this.formSetup();
|
|
3955
|
+
this.formService.initializeForm(this.dataForm);
|
|
3956
|
+
this.data$.pipe(takeUntil(this.destroy$)).subscribe((data) => {
|
|
3957
|
+
this.formService.model = data;
|
|
3958
|
+
this.formService.loading = false;
|
|
3959
|
+
this.dataForm.patchValue(data);
|
|
3960
|
+
if (this.beginEditOnEntityLoad) {
|
|
3961
|
+
this.formService.beginEdit();
|
|
3962
|
+
}
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
//#endregion
|
|
3966
|
+
//#region Event handlers
|
|
3967
|
+
//#endregion
|
|
3968
|
+
//#region Public methods
|
|
3969
|
+
toggle(entityID) {
|
|
3970
|
+
this.formService.model = null;
|
|
3971
|
+
this.formService.loading = true;
|
|
3972
|
+
this.dataForm.reset();
|
|
3973
|
+
if (!this.modalComponent.isOpen()) {
|
|
3974
|
+
this.entityIdSubject.next(entityID);
|
|
3975
|
+
}
|
|
3976
|
+
else {
|
|
3977
|
+
this.formService.resetForm();
|
|
3978
|
+
}
|
|
3979
|
+
this.modalComponent.toggleModal();
|
|
3980
|
+
}
|
|
3981
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ViewModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3982
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ViewModal, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "modalComponent", first: true, predicate: ["modal"], descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
|
|
3983
|
+
}
|
|
3984
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ViewModal, decorators: [{
|
|
3985
|
+
type: Component,
|
|
3986
|
+
args: [{ template: '' }]
|
|
3987
|
+
}], ctorParameters: () => [], propDecorators: { modalComponent: [{
|
|
3988
|
+
type: ViewChild,
|
|
3989
|
+
args: ['modal']
|
|
3990
|
+
}] } });
|
|
3991
|
+
|
|
3992
|
+
/**
|
|
3993
|
+
* Generated bundle index. Do not edit.
|
|
3994
|
+
*/
|
|
3995
|
+
|
|
3996
|
+
export { APP_CONFIG, AppConfig, AuthService, BackendFormValidationHelper, BaseButton, BaseFormViewButton, ButtonComponent, ButtonConfirmComponent, ButtonDeleteComponent, ButtonEditComponent, ButtonEditLegacyComponent, ButtonExportComponent, ButtonFiltersComponent, ButtonNewComponent, ButtonNewLegacyComponent, ButtonOpenRecordComponent, ButtonOpenRecordLegacyComponent, ButtonRefreshComponent, ButtonSaveComponent, ButtonSaveLegacyComponent, ButtonViewsComponent, ButtonViewsLegacyComponent, ChildList, ChildListLegacy, ConfirmModalComponent, CustomReuseStrategy, DefaultDetailsTabViewComponent, DefaultTabViewComponent, ErrorModalComponent, FRAMEWORK_VIEW_TYPE, FormView, FrameworkButtonsModule, FrameworkComponentsModule, FrameworkGridConfigsProvider, FrameworkModalsModule, FrameworkModule, FrameworkViewType, FrameworkViewsModule, LegacySubViewForm, LegacyTabViewDetails, LegacyTabViewList, ListView, MaxExportRows, ModalBase, MultiEditorLegacyModal, MultiEditorModal, MultiSelectModal, RouteHelper, Tab, TabBreadcrumbsComponent, TabService, TabViewBase, TabViewList, TabViewService, TabsComponent, ViewBase, ViewModal, ZAMBON_FRAMEWORK_I18N_ASSET, ZAMBON_FRAMEWORK_I18N_ASSET_PATH, ZAMBON_FRAMEWORK_I18N_RESOURCES, createZambonFrameworkTranslateLoader, provideZambonFrameworkTranslateLoader, provideZambonFrameworkTranslateService };
|
|
3997
|
+
//# sourceMappingURL=zambon-dev-ui-framework.mjs.map
|