@praxisui/crud 6.0.0-beta.0 → 8.0.0-beta.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/README.md +97 -1
- package/drawer-adapter/index.d.ts +2 -0
- package/fesm2022/praxisui-crud.mjs +3163 -70
- package/index.d.ts +273 -15
- package/package.json +6 -6
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, InjectionToken, inject, EventEmitter, DestroyRef, ChangeDetectorRef, ViewChild, Output, Input,
|
|
2
|
+
import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, ViewChild, Output, Input, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { HttpClient } from '@angular/common/http';
|
|
4
5
|
import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|
5
6
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
6
|
-
import { firstValueFrom } from 'rxjs';
|
|
7
|
-
import * as i2 from '@praxisui/core';
|
|
8
|
-
import { ASYNC_CONFIG_STORAGE, GlobalConfigService, fillUndefined,
|
|
9
|
-
import {
|
|
7
|
+
import { firstValueFrom, BehaviorSubject } from 'rxjs';
|
|
8
|
+
import * as i2$1 from '@praxisui/core';
|
|
9
|
+
import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, providePraxisI18nConfig, createDefaultTableConfig, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, GLOBAL_SURFACE_SERVICE, ComponentKeyService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconDirective, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
|
|
10
|
+
import { SettingsPanelService } from '@praxisui/settings-panel';
|
|
11
|
+
import { PraxisTableInlineAuthoringEditorComponent, PraxisTable } from '@praxisui/table';
|
|
12
|
+
import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
|
|
10
13
|
import * as i1 from '@angular/material/dialog';
|
|
11
14
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
|
12
15
|
export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
|
|
13
|
-
import * as i1$
|
|
16
|
+
import * as i1$2 from '@angular/common';
|
|
14
17
|
import { CommonModule } from '@angular/common';
|
|
15
|
-
import * as
|
|
18
|
+
import * as i1$1 from '@angular/forms';
|
|
19
|
+
import { FormsModule } from '@angular/forms';
|
|
20
|
+
import * as i2 from '@angular/material/button';
|
|
16
21
|
import { MatButtonModule } from '@angular/material/button';
|
|
17
|
-
import * as
|
|
22
|
+
import * as i3 from '@angular/material/card';
|
|
23
|
+
import { MatCardModule } from '@angular/material/card';
|
|
24
|
+
import * as i4 from '@angular/material/expansion';
|
|
25
|
+
import { MatExpansionModule } from '@angular/material/expansion';
|
|
26
|
+
import * as i5 from '@angular/material/form-field';
|
|
27
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
28
|
+
import * as i6 from '@angular/material/input';
|
|
29
|
+
import { MatInputModule } from '@angular/material/input';
|
|
30
|
+
import * as i7 from '@angular/material/select';
|
|
31
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
32
|
+
import * as i8 from '@angular/material/slide-toggle';
|
|
33
|
+
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
34
|
+
import * as i5$1 from '@angular/material/icon';
|
|
18
35
|
import { MatIconModule } from '@angular/material/icon';
|
|
19
36
|
import { PraxisDynamicForm } from '@praxisui/dynamic-form';
|
|
20
|
-
import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
|
|
21
37
|
import { filter, take } from 'rxjs/operators';
|
|
22
38
|
|
|
23
39
|
class DialogService {
|
|
@@ -57,6 +73,7 @@ class CrudLauncherService {
|
|
|
57
73
|
dialog = inject(DialogService);
|
|
58
74
|
storage = inject(ASYNC_CONFIG_STORAGE);
|
|
59
75
|
global = inject(GlobalConfigService);
|
|
76
|
+
operationResolver = inject(CrudOperationResolutionService);
|
|
60
77
|
drawerAdapter = (() => {
|
|
61
78
|
try {
|
|
62
79
|
return inject(CRUD_DRAWER_ADAPTER);
|
|
@@ -65,7 +82,7 @@ class CrudLauncherService {
|
|
|
65
82
|
return undefined;
|
|
66
83
|
}
|
|
67
84
|
})();
|
|
68
|
-
async launch(action, row, metadata, componentKeyId, drawerCallbacks) {
|
|
85
|
+
async launch(action, row, metadata, componentKeyId, drawerCallbacks, runtime) {
|
|
69
86
|
// Carregar overrides de CRUD (se houver) e mesclar em uma cópia local
|
|
70
87
|
const merged = await this.mergeCrudOverrides(metadata, action, componentKeyId || undefined);
|
|
71
88
|
const mode = this.resolveOpenMode(merged.action, merged.metadata);
|
|
@@ -79,13 +96,14 @@ class CrudLauncherService {
|
|
|
79
96
|
return { mode };
|
|
80
97
|
}
|
|
81
98
|
if (mode === 'drawer' && this.drawerAdapter) {
|
|
82
|
-
const
|
|
99
|
+
const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
|
|
100
|
+
const inputs = this.mapInputs(actionForLaunch, row, merged.metadata, runtime);
|
|
83
101
|
const idField = merged.metadata.resource?.idField ?? 'id';
|
|
84
102
|
if (row && inputs[idField] === undefined && row[idField] !== undefined) {
|
|
85
103
|
inputs[idField] = row[idField];
|
|
86
104
|
}
|
|
87
105
|
await Promise.resolve(this.drawerAdapter.open({
|
|
88
|
-
action:
|
|
106
|
+
action: actionForLaunch,
|
|
89
107
|
metadata: merged.metadata,
|
|
90
108
|
inputs,
|
|
91
109
|
onClose: drawerCallbacks?.onClose,
|
|
@@ -93,18 +111,22 @@ class CrudLauncherService {
|
|
|
93
111
|
}));
|
|
94
112
|
return { mode };
|
|
95
113
|
}
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
|
|
115
|
+
if (!actionForLaunch.formId) {
|
|
116
|
+
throw new Error(`formId not provided for action ${actionForLaunch.action}`);
|
|
98
117
|
}
|
|
99
|
-
const inputs = this.mapInputs(merged.
|
|
118
|
+
const inputs = this.mapInputs(actionForLaunch, row, merged.metadata, runtime);
|
|
100
119
|
const idField = merged.metadata.resource?.idField ?? 'id';
|
|
101
|
-
if (
|
|
120
|
+
if (actionForLaunch.action !== 'create' &&
|
|
121
|
+
row &&
|
|
122
|
+
inputs[idField] === undefined &&
|
|
123
|
+
row[idField] !== undefined) {
|
|
102
124
|
inputs[idField] = row[idField];
|
|
103
125
|
}
|
|
104
126
|
const modalCfg = { ...(merged.metadata.defaults?.modal || {}) };
|
|
105
127
|
console.debug('[CRUD:Launcher] opening dialog with:', {
|
|
106
128
|
action: merged.action.action,
|
|
107
|
-
formId:
|
|
129
|
+
formId: actionForLaunch.formId,
|
|
108
130
|
inputs,
|
|
109
131
|
modalCfg,
|
|
110
132
|
resourcePath: merged.metadata.resource?.path ?? merged.metadata.table?.resourcePath,
|
|
@@ -137,7 +159,7 @@ class CrudLauncherService {
|
|
|
137
159
|
minWidth: '360px',
|
|
138
160
|
maxWidth: '95vw',
|
|
139
161
|
ariaLabelledBy: 'crudDialogTitle',
|
|
140
|
-
data: { action:
|
|
162
|
+
data: { action: actionForLaunch, row, metadata: merged.metadata, inputs },
|
|
141
163
|
});
|
|
142
164
|
return { mode, ref };
|
|
143
165
|
}
|
|
@@ -152,8 +174,10 @@ class CrudLauncherService {
|
|
|
152
174
|
const actionName = action.action;
|
|
153
175
|
const globalMode = (actionName && globalCrud?.actionDefaults?.[actionName]?.openMode) ?? globalCrud?.defaults?.openMode;
|
|
154
176
|
let resolved = globalMode ?? 'route';
|
|
155
|
-
// Safety: if modal/drawer but no formId, degrade to route
|
|
156
|
-
if ((resolved === 'modal' || resolved === 'drawer') &&
|
|
177
|
+
// Safety: if modal/drawer but there is no formId and the action cannot be inferred, degrade to route.
|
|
178
|
+
if ((resolved === 'modal' || resolved === 'drawer') &&
|
|
179
|
+
!action.formId &&
|
|
180
|
+
!this.isCanonicalCrudAction(action.action)) {
|
|
157
181
|
resolved = 'route';
|
|
158
182
|
}
|
|
159
183
|
return resolved;
|
|
@@ -201,30 +225,116 @@ class CrudLauncherService {
|
|
|
201
225
|
const queryString = new URLSearchParams(query).toString();
|
|
202
226
|
return queryString ? `${route}?${queryString}` : route;
|
|
203
227
|
}
|
|
204
|
-
mapInputs(action, row) {
|
|
228
|
+
mapInputs(action, row, metadata, runtime) {
|
|
205
229
|
const inputs = {};
|
|
206
230
|
action.params?.forEach((p) => {
|
|
207
231
|
if (p.to === 'input') {
|
|
208
232
|
inputs[p.name] = row?.[p.from];
|
|
209
233
|
}
|
|
210
234
|
});
|
|
211
|
-
|
|
212
|
-
|
|
235
|
+
const resolved = this.resolveActionFormContract(action, row, metadata, runtime);
|
|
236
|
+
if (resolved.schemaUrl) {
|
|
237
|
+
inputs['schemaUrl'] = resolved.schemaUrl;
|
|
238
|
+
}
|
|
239
|
+
if (resolved.submitUrl) {
|
|
240
|
+
inputs['submitUrl'] = resolved.submitUrl;
|
|
213
241
|
}
|
|
214
|
-
if (
|
|
215
|
-
inputs['
|
|
242
|
+
if (resolved.submitMethod) {
|
|
243
|
+
inputs['submitMethod'] = resolved.submitMethod;
|
|
216
244
|
}
|
|
217
|
-
if (
|
|
218
|
-
inputs['
|
|
245
|
+
if (resolved.apiEndpointKey != null) {
|
|
246
|
+
inputs['apiEndpointKey'] = resolved.apiEndpointKey;
|
|
219
247
|
}
|
|
220
|
-
if (
|
|
221
|
-
inputs['
|
|
248
|
+
if (resolved.apiUrlEntry != null) {
|
|
249
|
+
inputs['apiUrlEntry'] = resolved.apiUrlEntry;
|
|
222
250
|
}
|
|
223
|
-
if (action.form?.
|
|
224
|
-
inputs['
|
|
251
|
+
if (action.form?.initialValue != null) {
|
|
252
|
+
inputs['initialValue'] = action.form.initialValue;
|
|
225
253
|
}
|
|
226
254
|
return inputs;
|
|
227
255
|
}
|
|
256
|
+
resolveActionForLaunch(action, metadata) {
|
|
257
|
+
if (action.formId) {
|
|
258
|
+
return action;
|
|
259
|
+
}
|
|
260
|
+
if (!this.isCanonicalCrudAction(action.action)) {
|
|
261
|
+
return action;
|
|
262
|
+
}
|
|
263
|
+
const formId = this.buildInferredFormId(action, metadata);
|
|
264
|
+
return formId ? { ...action, formId } : action;
|
|
265
|
+
}
|
|
266
|
+
resolveActionFormContract(action, row, metadata, runtime) {
|
|
267
|
+
if (this.isExplicitCrudAction(action)) {
|
|
268
|
+
return {
|
|
269
|
+
schemaUrl: action.form?.schemaUrl ?? null,
|
|
270
|
+
submitUrl: action.form?.submitUrl ?? null,
|
|
271
|
+
submitMethod: action.form?.submitMethod ?? null,
|
|
272
|
+
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
273
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (!this.isCanonicalCrudAction(action.action)) {
|
|
277
|
+
return {
|
|
278
|
+
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
279
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
|
|
283
|
+
if (!resourcePath) {
|
|
284
|
+
return {
|
|
285
|
+
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
286
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const idField = String(metadata.resource?.idField ?? 'id');
|
|
290
|
+
const resourceId = row?.[idField];
|
|
291
|
+
const resolved = this.operationResolver.resolve({
|
|
292
|
+
operation: action.action,
|
|
293
|
+
resourcePath,
|
|
294
|
+
resourceId: resourceId ?? null,
|
|
295
|
+
capabilities: runtime?.capabilities ?? null,
|
|
296
|
+
links: runtime?.links ?? null,
|
|
297
|
+
explicit: null,
|
|
298
|
+
endpointKey: action.form?.apiEndpointKey ??
|
|
299
|
+
metadata.resource?.endpointKey ??
|
|
300
|
+
undefined,
|
|
301
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
302
|
+
});
|
|
303
|
+
return {
|
|
304
|
+
schemaUrl: resolved?.schemaUrl ?? null,
|
|
305
|
+
submitUrl: resolved?.submitUrl ?? null,
|
|
306
|
+
submitMethod: resolved?.submitMethod ?? null,
|
|
307
|
+
apiEndpointKey: action.form?.apiEndpointKey ?? metadata.resource?.endpointKey ?? null,
|
|
308
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
resolveRuntimeContract(action, row, metadata, runtime) {
|
|
312
|
+
return this.resolveActionFormContract(action, row, metadata, runtime);
|
|
313
|
+
}
|
|
314
|
+
buildInferredFormId(action, metadata) {
|
|
315
|
+
const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
|
|
316
|
+
if (!resourcePath) {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
const sanitizedResource = resourcePath
|
|
320
|
+
.replace(/^\/+/, '')
|
|
321
|
+
.replace(/\/+$/, '')
|
|
322
|
+
.replace(/[^a-zA-Z0-9/_-]+/g, '')
|
|
323
|
+
.replace(/[\/_]+/g, '-');
|
|
324
|
+
return `${sanitizedResource || 'crud'}-${String(action.action || '').trim().toLowerCase()}`;
|
|
325
|
+
}
|
|
326
|
+
isCanonicalCrudAction(actionName) {
|
|
327
|
+
const normalized = String(actionName || '').trim().toLowerCase();
|
|
328
|
+
return normalized === 'create' || normalized === 'view' || normalized === 'edit' || normalized === 'delete';
|
|
329
|
+
}
|
|
330
|
+
isExplicitCrudAction(action) {
|
|
331
|
+
if (action.mode === 'explicit') {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
return !!(String(action.form?.schemaUrl || '').trim() ||
|
|
335
|
+
String(action.form?.submitUrl || '').trim() ||
|
|
336
|
+
String(action.form?.submitMethod || '').trim());
|
|
337
|
+
}
|
|
228
338
|
async mergeCrudOverrides(metadata, action, componentKeyId) {
|
|
229
339
|
try {
|
|
230
340
|
if (!componentKeyId)
|
|
@@ -280,6 +390,294 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
280
390
|
args: [{ providedIn: 'root' }]
|
|
281
391
|
}] });
|
|
282
392
|
|
|
393
|
+
const DOCUMENT_KIND = 'praxis.crud.editor';
|
|
394
|
+
const DOCUMENT_VERSION = 1;
|
|
395
|
+
const CANONICAL_ACTIONS = ['create', 'view', 'edit', 'delete'];
|
|
396
|
+
const OPEN_MODES$1 = ['route', 'modal', 'drawer'];
|
|
397
|
+
function createCrudAuthoringDocument(source) {
|
|
398
|
+
return normalizeCrudAuthoringDocument({
|
|
399
|
+
kind: DOCUMENT_KIND,
|
|
400
|
+
version: DOCUMENT_VERSION,
|
|
401
|
+
metadata: asCrudMetadata(source?.metadata),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
function parseLegacyOrCrudDocument(raw) {
|
|
405
|
+
const obj = asRecord(raw);
|
|
406
|
+
if (obj.kind === DOCUMENT_KIND && obj.version === DOCUMENT_VERSION) {
|
|
407
|
+
return normalizeCrudAuthoringDocument({
|
|
408
|
+
kind: DOCUMENT_KIND,
|
|
409
|
+
version: DOCUMENT_VERSION,
|
|
410
|
+
metadata: asCrudMetadata(obj.metadata),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (obj.document) {
|
|
414
|
+
return parseLegacyOrCrudDocument(obj.document);
|
|
415
|
+
}
|
|
416
|
+
if (isCrudMetadataLike(obj)) {
|
|
417
|
+
return createCrudAuthoringDocument({ metadata: obj });
|
|
418
|
+
}
|
|
419
|
+
if (isCrudMetadataLike(asRecord(obj.metadata))) {
|
|
420
|
+
return createCrudAuthoringDocument({ metadata: obj.metadata });
|
|
421
|
+
}
|
|
422
|
+
return createCrudAuthoringDocument({ metadata: raw });
|
|
423
|
+
}
|
|
424
|
+
function normalizeCrudAuthoringDocument(doc) {
|
|
425
|
+
return {
|
|
426
|
+
kind: DOCUMENT_KIND,
|
|
427
|
+
version: DOCUMENT_VERSION,
|
|
428
|
+
metadata: normalizeCrudMetadata(doc?.metadata),
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function serializeCrudAuthoringDocument(doc) {
|
|
432
|
+
const normalized = normalizeCrudAuthoringDocument(doc);
|
|
433
|
+
return stripUndefinedDeep({
|
|
434
|
+
kind: DOCUMENT_KIND,
|
|
435
|
+
version: DOCUMENT_VERSION,
|
|
436
|
+
metadata: normalized.metadata,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function validateCrudAuthoringDocument(doc, context) {
|
|
440
|
+
const diagnostics = [];
|
|
441
|
+
const normalized = normalizeCrudAuthoringDocument(doc);
|
|
442
|
+
const metadata = normalized.metadata;
|
|
443
|
+
if (metadata.component !== 'praxis-crud') {
|
|
444
|
+
diagnostics.push(errorDiagnostic('crud.metadata.component.invalid', 'metadata.component must be praxis-crud', 'metadata.component'));
|
|
445
|
+
}
|
|
446
|
+
if (!metadata.table || typeof metadata.table !== 'object') {
|
|
447
|
+
diagnostics.push(errorDiagnostic('crud.metadata.table.required', 'metadata.table is required', 'metadata.table'));
|
|
448
|
+
}
|
|
449
|
+
if (metadata.resource &&
|
|
450
|
+
'path' in metadata.resource &&
|
|
451
|
+
!trimString(metadata.resource.path)) {
|
|
452
|
+
diagnostics.push(errorDiagnostic('crud.metadata.resource.path.required', 'metadata.resource.path must not be empty when resource is provided', 'metadata.resource.path'));
|
|
453
|
+
}
|
|
454
|
+
if (!trimString(metadata.resource?.path) && !Array.isArray(metadata.data)) {
|
|
455
|
+
diagnostics.push(warnDiagnostic('crud.metadata.resource-or-data.missing', 'Provide metadata.resource.path or metadata.data so the CRUD can resolve its source', 'metadata.resource.path'));
|
|
456
|
+
}
|
|
457
|
+
if (context?.requireCanonicalActions) {
|
|
458
|
+
for (const actionName of CANONICAL_ACTIONS) {
|
|
459
|
+
if (!findCrudAction(metadata.actions, actionName)) {
|
|
460
|
+
diagnostics.push(infoDiagnostic(`crud.metadata.actions.${actionName}.missing`, `Canonical CRUD action ${actionName} is not declared yet`, `metadata.actions.${actionName}`));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const action of metadata.actions || []) {
|
|
465
|
+
validateCrudAction(action, metadata, diagnostics);
|
|
466
|
+
}
|
|
467
|
+
return diagnostics;
|
|
468
|
+
}
|
|
469
|
+
function findCrudAction(actions, actionName) {
|
|
470
|
+
return (actions || []).find((action) => action?.action === actionName);
|
|
471
|
+
}
|
|
472
|
+
function validateCrudAction(action, metadata, diagnostics) {
|
|
473
|
+
const actionName = trimString(action?.action) || 'unknown';
|
|
474
|
+
const effectiveOpenMode = normalizeOpenMode(action?.openMode ?? metadata.defaults?.openMode) || 'route';
|
|
475
|
+
const inferredCanonical = isCanonicalCrudAction$1(action?.action) && !isExplicitCrudAction$1(action);
|
|
476
|
+
if (action.openMode && !normalizeOpenMode(action.openMode)) {
|
|
477
|
+
diagnostics.push(errorDiagnostic('crud.metadata.actions.openMode.invalid', 'action.openMode must be route, modal or drawer', `metadata.actions.${actionName}.openMode`));
|
|
478
|
+
}
|
|
479
|
+
if (effectiveOpenMode === 'route' && !trimString(action.route)) {
|
|
480
|
+
diagnostics.push(errorDiagnostic('crud.metadata.actions.route.required', 'route is required when the effective open mode is route', `metadata.actions.${actionName}.route`));
|
|
481
|
+
}
|
|
482
|
+
if ((effectiveOpenMode === 'modal' || effectiveOpenMode === 'drawer') &&
|
|
483
|
+
!trimString(action.formId) &&
|
|
484
|
+
!inferredCanonical) {
|
|
485
|
+
diagnostics.push(errorDiagnostic('crud.metadata.actions.formId.required', 'formId is required when the effective open mode is modal or drawer', `metadata.actions.${actionName}.formId`));
|
|
486
|
+
}
|
|
487
|
+
const hasSubmitUrl = !!trimString(action.form?.submitUrl);
|
|
488
|
+
const hasSubmitMethod = !!trimString(action.form?.submitMethod);
|
|
489
|
+
if (hasSubmitUrl !== hasSubmitMethod) {
|
|
490
|
+
diagnostics.push(errorDiagnostic('crud.metadata.actions.form.submit-contract.invalid', 'form.submitUrl and form.submitMethod must be provided together', `metadata.actions.${actionName}.form`));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function normalizeCrudMetadata(metadata) {
|
|
494
|
+
const base = cloneJson(metadata || {
|
|
495
|
+
component: 'praxis-crud',
|
|
496
|
+
table: { columns: [] },
|
|
497
|
+
});
|
|
498
|
+
const hasExplicitComponent = Object.prototype.hasOwnProperty.call(base, 'component');
|
|
499
|
+
const hasExplicitTable = Object.prototype.hasOwnProperty.call(base, 'table');
|
|
500
|
+
const normalized = {
|
|
501
|
+
...base,
|
|
502
|
+
component: hasExplicitComponent && typeof base.component === 'string'
|
|
503
|
+
? base.component
|
|
504
|
+
: 'praxis-crud',
|
|
505
|
+
table: (hasExplicitTable ? base.table : { columns: [] }),
|
|
506
|
+
};
|
|
507
|
+
if (base.resource && typeof base.resource === 'object') {
|
|
508
|
+
normalized.resource = stripUndefinedShallow({
|
|
509
|
+
path: trimString(base.resource.path) || undefined,
|
|
510
|
+
idField: typeof base.resource.idField === 'string'
|
|
511
|
+
? trimString(base.resource.idField) || undefined
|
|
512
|
+
: base.resource.idField,
|
|
513
|
+
endpointKey: trimString(base.resource.endpointKey) || undefined,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
if (base.defaults && typeof base.defaults === 'object') {
|
|
517
|
+
normalized.defaults = stripUndefinedDeep({
|
|
518
|
+
...base.defaults,
|
|
519
|
+
openMode: normalizeOpenMode(base.defaults.openMode),
|
|
520
|
+
back: normalizeBackConfig(base.defaults.back),
|
|
521
|
+
header: base.defaults.header
|
|
522
|
+
? {
|
|
523
|
+
...base.defaults.header,
|
|
524
|
+
backLabel: trimString(base.defaults.header.backLabel) || undefined,
|
|
525
|
+
variant: normalizeHeaderVariant(base.defaults.header.variant),
|
|
526
|
+
}
|
|
527
|
+
: undefined,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
if (Array.isArray(base.actions)) {
|
|
531
|
+
normalized.actions = [...base.actions]
|
|
532
|
+
.map((action) => normalizeCrudAction(action))
|
|
533
|
+
.sort(compareCrudActions);
|
|
534
|
+
}
|
|
535
|
+
return stripUndefinedDeep(normalized);
|
|
536
|
+
}
|
|
537
|
+
function normalizeCrudAction(action) {
|
|
538
|
+
return stripUndefinedDeep({
|
|
539
|
+
...action,
|
|
540
|
+
action: trimString(action?.action) || action?.action,
|
|
541
|
+
id: trimString(action?.id) || undefined,
|
|
542
|
+
label: trimString(action?.label) || undefined,
|
|
543
|
+
openMode: normalizeOpenMode(action?.openMode),
|
|
544
|
+
route: trimString(action?.route) || undefined,
|
|
545
|
+
formId: trimString(action?.formId) || undefined,
|
|
546
|
+
back: normalizeBackConfig(action?.back),
|
|
547
|
+
params: Array.isArray(action?.params)
|
|
548
|
+
? action.params
|
|
549
|
+
.map((param) => stripUndefinedDeep({
|
|
550
|
+
from: trimString(param?.from) || undefined,
|
|
551
|
+
to: normalizeParamTarget(param?.to),
|
|
552
|
+
name: trimString(param?.name) || undefined,
|
|
553
|
+
}))
|
|
554
|
+
.filter((param) => !!(param.from || param.to || param.name))
|
|
555
|
+
: undefined,
|
|
556
|
+
form: action?.form
|
|
557
|
+
? {
|
|
558
|
+
schemaUrl: trimString(action.form.schemaUrl) || undefined,
|
|
559
|
+
submitUrl: trimString(action.form.submitUrl) || undefined,
|
|
560
|
+
submitMethod: trimString(action.form.submitMethod) || undefined,
|
|
561
|
+
apiEndpointKey: trimString(action.form.apiEndpointKey) || undefined,
|
|
562
|
+
apiUrlEntry: action.form.apiUrlEntry || undefined,
|
|
563
|
+
initialValue: normalizeInitialValue(action.form.initialValue),
|
|
564
|
+
}
|
|
565
|
+
: undefined,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function isCanonicalCrudAction$1(actionName) {
|
|
569
|
+
const normalized = trimString(actionName) || '';
|
|
570
|
+
return CANONICAL_ACTIONS.includes(normalized);
|
|
571
|
+
}
|
|
572
|
+
function isExplicitCrudAction$1(action) {
|
|
573
|
+
if (action?.mode === 'explicit') {
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
return !!(trimString(action?.form?.schemaUrl) ||
|
|
577
|
+
trimString(action?.form?.submitUrl) ||
|
|
578
|
+
trimString(action?.form?.submitMethod));
|
|
579
|
+
}
|
|
580
|
+
function compareCrudActions(left, right) {
|
|
581
|
+
return resolveActionWeight(left?.action) - resolveActionWeight(right?.action);
|
|
582
|
+
}
|
|
583
|
+
function resolveActionWeight(actionName) {
|
|
584
|
+
const normalized = trimString(actionName) || '';
|
|
585
|
+
const index = CANONICAL_ACTIONS.indexOf(normalized);
|
|
586
|
+
return index >= 0 ? index : CANONICAL_ACTIONS.length + normalized.localeCompare('');
|
|
587
|
+
}
|
|
588
|
+
function normalizeOpenMode(value) {
|
|
589
|
+
const trimmed = trimString(value);
|
|
590
|
+
return OPEN_MODES$1.includes(trimmed)
|
|
591
|
+
? trimmed
|
|
592
|
+
: undefined;
|
|
593
|
+
}
|
|
594
|
+
function normalizeHeaderVariant(value) {
|
|
595
|
+
const trimmed = trimString(value);
|
|
596
|
+
return trimmed && ['ghost', 'tonal', 'outlined'].includes(trimmed)
|
|
597
|
+
? trimmed
|
|
598
|
+
: undefined;
|
|
599
|
+
}
|
|
600
|
+
function normalizeBackConfig(value) {
|
|
601
|
+
if (!value || typeof value !== 'object') {
|
|
602
|
+
return undefined;
|
|
603
|
+
}
|
|
604
|
+
const back = value;
|
|
605
|
+
const strategy = trimString(back['strategy']);
|
|
606
|
+
return stripUndefinedDeep({
|
|
607
|
+
strategy: strategy && ['auto', 'close', 'navigate'].includes(strategy) ? strategy : undefined,
|
|
608
|
+
returnTo: trimString(back['returnTo']) || undefined,
|
|
609
|
+
confirmOnDirty: typeof back['confirmOnDirty'] === 'boolean' ? back['confirmOnDirty'] : undefined,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
function normalizeParamTarget(value) {
|
|
613
|
+
const trimmed = trimString(value);
|
|
614
|
+
return trimmed && ['routeParam', 'query', 'input'].includes(trimmed)
|
|
615
|
+
? trimmed
|
|
616
|
+
: undefined;
|
|
617
|
+
}
|
|
618
|
+
function normalizeInitialValue(value) {
|
|
619
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
620
|
+
? cloneJson(value)
|
|
621
|
+
: undefined;
|
|
622
|
+
}
|
|
623
|
+
function isCrudMetadataLike(value) {
|
|
624
|
+
return value.component === 'praxis-crud';
|
|
625
|
+
}
|
|
626
|
+
function asCrudMetadata(raw) {
|
|
627
|
+
return (raw && typeof raw === 'object'
|
|
628
|
+
? raw
|
|
629
|
+
: { component: 'praxis-crud', table: { columns: [] } });
|
|
630
|
+
}
|
|
631
|
+
function asRecord(raw) {
|
|
632
|
+
return raw && typeof raw === 'object' ? raw : {};
|
|
633
|
+
}
|
|
634
|
+
function trimString(raw) {
|
|
635
|
+
if (typeof raw !== 'string')
|
|
636
|
+
return undefined;
|
|
637
|
+
const trimmed = raw.trim();
|
|
638
|
+
return trimmed ? trimmed : undefined;
|
|
639
|
+
}
|
|
640
|
+
function cloneJson(value) {
|
|
641
|
+
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
642
|
+
}
|
|
643
|
+
function stripUndefinedShallow(value) {
|
|
644
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
645
|
+
}
|
|
646
|
+
function stripUndefinedDeep(value) {
|
|
647
|
+
if (Array.isArray(value)) {
|
|
648
|
+
return value
|
|
649
|
+
.map((entry) => stripUndefinedDeep(entry))
|
|
650
|
+
.filter((entry) => entry !== undefined);
|
|
651
|
+
}
|
|
652
|
+
if (value && typeof value === 'object') {
|
|
653
|
+
return Object.fromEntries(Object.entries(value)
|
|
654
|
+
.map(([key, entry]) => [key, stripUndefinedDeep(entry)])
|
|
655
|
+
.filter(([, entry]) => entry !== undefined));
|
|
656
|
+
}
|
|
657
|
+
return value;
|
|
658
|
+
}
|
|
659
|
+
function errorDiagnostic(code, message, path) {
|
|
660
|
+
return { level: 'error', code, message, path };
|
|
661
|
+
}
|
|
662
|
+
function warnDiagnostic(code, message, path) {
|
|
663
|
+
return { level: 'warning', code, message, path };
|
|
664
|
+
}
|
|
665
|
+
function infoDiagnostic(code, message, path) {
|
|
666
|
+
return { level: 'info', code, message, path };
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function isCanonicalCrudAction(actionName) {
|
|
670
|
+
const normalized = String(actionName || '').trim().toLowerCase();
|
|
671
|
+
return normalized === 'create' || normalized === 'view' || normalized === 'edit' || normalized === 'delete';
|
|
672
|
+
}
|
|
673
|
+
function isExplicitCrudAction(action) {
|
|
674
|
+
if (action.mode === 'explicit') {
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
return !!(String(action.form?.schemaUrl || '').trim() ||
|
|
678
|
+
String(action.form?.submitUrl || '').trim() ||
|
|
679
|
+
String(action.form?.submitMethod || '').trim());
|
|
680
|
+
}
|
|
283
681
|
function assertCrudMetadata(meta, options = {}) {
|
|
284
682
|
if (meta.component !== 'praxis-crud') {
|
|
285
683
|
throw new Error('Invalid component type for CRUD metadata');
|
|
@@ -289,12 +687,14 @@ function assertCrudMetadata(meta, options = {}) {
|
|
|
289
687
|
}
|
|
290
688
|
meta.actions?.forEach((action) => {
|
|
291
689
|
const mode = action.openMode ?? meta.defaults?.openMode ?? 'route';
|
|
690
|
+
const inferredCanonical = !isExplicitCrudAction(action) && isCanonicalCrudAction(action.action);
|
|
292
691
|
if (!options.allowDeferredActionBindings && mode === 'route' && !action.route) {
|
|
293
692
|
throw new Error(`Route not provided for action ${action.action}`);
|
|
294
693
|
}
|
|
295
694
|
if (!options.allowDeferredActionBindings &&
|
|
296
695
|
(mode === 'modal' || mode === 'drawer') &&
|
|
297
|
-
!action.formId
|
|
696
|
+
!action.formId &&
|
|
697
|
+
!inferredCanonical) {
|
|
298
698
|
throw new Error(`formId not provided for action ${action.action}`);
|
|
299
699
|
}
|
|
300
700
|
action.params?.forEach((p) => {
|
|
@@ -323,6 +723,8 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
323
723
|
'crud.actions.view': 'Ver',
|
|
324
724
|
'crud.actions.edit': 'Editar',
|
|
325
725
|
'crud.actions.delete': 'Excluir',
|
|
726
|
+
'crud.delete.confirmMessage': 'Esta ação não pode ser desfeita. Deseja continuar?',
|
|
727
|
+
'crud.delete.cancel': 'Cancelar',
|
|
326
728
|
},
|
|
327
729
|
'en-US': {
|
|
328
730
|
'crud.emptyState.title': 'Connect CRUD to a resource',
|
|
@@ -333,6 +735,8 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
333
735
|
'crud.actions.view': 'View',
|
|
334
736
|
'crud.actions.edit': 'Edit',
|
|
335
737
|
'crud.actions.delete': 'Delete',
|
|
738
|
+
'crud.delete.confirmMessage': 'This action cannot be undone. Do you want to continue?',
|
|
739
|
+
'crud.delete.cancel': 'Cancel',
|
|
336
740
|
},
|
|
337
741
|
},
|
|
338
742
|
},
|
|
@@ -372,6 +776,2526 @@ function translateCrudRuntimeText(i18n, key, fallback, params, locale) {
|
|
|
372
776
|
return i18n.t(key, params, runtimeFallback, PRAXIS_CRUD_RUNTIME_I18N_NAMESPACE);
|
|
373
777
|
}
|
|
374
778
|
|
|
779
|
+
const PRAXIS_CRUD_AUTHORING_I18N_NAMESPACE = 'praxisCrudAuthoring';
|
|
780
|
+
const PRAXIS_CRUD_AUTHORING_I18N_CONFIG = {
|
|
781
|
+
namespaces: {
|
|
782
|
+
[PRAXIS_CRUD_AUTHORING_I18N_NAMESPACE]: {
|
|
783
|
+
'pt-BR': {
|
|
784
|
+
'crud.authoring.title': 'Configurações do CRUD',
|
|
785
|
+
'crud.authoring.subtitle': 'Edite o documento canônico do fluxo CRUD sem deslocar a semântica para o host.',
|
|
786
|
+
'crud.authoring.section.connection': 'Conexão',
|
|
787
|
+
'crud.authoring.section.actions': 'Ações',
|
|
788
|
+
'crud.authoring.section.defaults': 'Abertura e cabeçalho',
|
|
789
|
+
'crud.authoring.section.table': 'Tabela',
|
|
790
|
+
'crud.authoring.section.json': 'JSON',
|
|
791
|
+
'crud.authoring.overview.connection': 'Conexão',
|
|
792
|
+
'crud.authoring.overview.defaults': 'Defaults',
|
|
793
|
+
'crud.authoring.overview.actions': 'Ações',
|
|
794
|
+
'crud.authoring.overview.table': 'Tabela',
|
|
795
|
+
'crud.authoring.overview.connectionMissing': 'Recurso ainda não configurado',
|
|
796
|
+
'crud.authoring.overview.connectionIdField': 'ID: {value}',
|
|
797
|
+
'crud.authoring.overview.connectionEndpoint': 'API: {value}',
|
|
798
|
+
'crud.authoring.overview.connectionDetailsPending': 'Identificador e endpoint ainda são opcionais aqui.',
|
|
799
|
+
'crud.authoring.overview.backVisible': 'Voltar visível',
|
|
800
|
+
'crud.authoring.overview.backHidden': 'Voltar oculto',
|
|
801
|
+
'crud.authoring.overview.modalDensity': 'Modal: {value}',
|
|
802
|
+
'crud.authoring.overview.backStrategy': 'Voltar: {value}',
|
|
803
|
+
'crud.authoring.overview.modalRememberState': 'Estado lembrado',
|
|
804
|
+
'crud.authoring.overview.backConfirmOnDirty': 'Confirmar ao sair',
|
|
805
|
+
'crud.authoring.overview.headerSticky': 'Cabeçalho fixo',
|
|
806
|
+
'crud.authoring.overview.headerBreadcrumbs': 'Breadcrumbs',
|
|
807
|
+
'crud.authoring.overview.columns': 'colunas',
|
|
808
|
+
'crud.authoring.overview.columnsCount': '{count} colunas',
|
|
809
|
+
'crud.authoring.overview.validation': 'Validação',
|
|
810
|
+
'crud.authoring.overview.validationErrors': '{count} erros',
|
|
811
|
+
'crud.authoring.overview.validationWarnings': '{count} alertas',
|
|
812
|
+
'crud.authoring.overview.validationClean': 'Sem diagnósticos bloqueantes',
|
|
813
|
+
'crud.authoring.overview.validationGrouped': 'Agrupado por seção abaixo para troubleshooting mais rápido.',
|
|
814
|
+
'crud.authoring.overview.validationAligned': 'Orientação da shell e diagnósticos estão alinhados.',
|
|
815
|
+
'crud.authoring.overview.actionsReady': '{count} prontas',
|
|
816
|
+
'crud.authoring.overview.actionsPending': '{count} pendentes',
|
|
817
|
+
'crud.authoring.overview.actionsInvalid': '{count} inválidas',
|
|
818
|
+
'crud.authoring.overview.actionsTracked': '{count} blocos de ação rastreados',
|
|
819
|
+
'crud.authoring.overview.actionsNext': 'Próxima: {action}. {summary}',
|
|
820
|
+
'crud.authoring.section.status.ready': 'Pronta',
|
|
821
|
+
'crud.authoring.section.status.pending': 'Pede atenção',
|
|
822
|
+
'crud.authoring.section.status.invalid': 'Inválida',
|
|
823
|
+
'crud.authoring.section.actions.invalidSummary': '{count} blocos de ação têm erros de validação.',
|
|
824
|
+
'crud.authoring.section.actions.readySummary': 'Todas as ações principais do CRUD estão configuradas.',
|
|
825
|
+
'crud.authoring.section.actions.pendingSummary': '{count} de {total} blocos de ação estão prontos.',
|
|
826
|
+
'crud.authoring.section.actions.invalidSummaryDetailed': '{count} blocos de ação têm erros de validação. Comece por {action}.',
|
|
827
|
+
'crud.authoring.section.actions.pendingSummaryDetailed': '{count} de {total} blocos de ação estão prontos. Próxima: {action}.',
|
|
828
|
+
'crud.authoring.nextFocus.connection': 'Próximo foco: conexão',
|
|
829
|
+
'crud.authoring.nextFocus.defaults': 'Próximo foco: defaults',
|
|
830
|
+
'crud.authoring.nextFocus.actions': 'Próximo foco: ações',
|
|
831
|
+
'crud.authoring.nextFocus.table': 'Próximo foco: tabela',
|
|
832
|
+
'crud.authoring.nextFocus.invalid': 'Resolva os diagnósticos bloqueantes desta seção antes de seguir com confiança.',
|
|
833
|
+
'crud.authoring.nextFocus.pending': 'Esta é a próxima melhor seção para completar o fluxo CRUD canônico.',
|
|
834
|
+
'crud.authoring.nextFocus.actionsInvalid': 'Resolva os diagnósticos bloqueantes em {action}. {summary}.',
|
|
835
|
+
'crud.authoring.nextFocus.actionsPending': 'Complete {action} na sequência. {summary}.',
|
|
836
|
+
'crud.authoring.nextFocus.ready': 'Todas as seções rastreadas estão em bom estado.',
|
|
837
|
+
'crud.authoring.nextFocus.complete': 'Concluído',
|
|
838
|
+
'crud.authoring.nextFocus.completeTitle': 'Orientação da shell concluída',
|
|
839
|
+
'crud.authoring.health.invalid': 'Inválidas',
|
|
840
|
+
'crud.authoring.health.pending': 'Pedem atenção',
|
|
841
|
+
'crud.authoring.health.ready': 'Saudáveis',
|
|
842
|
+
'crud.authoring.health.invalidEmpty': 'Nenhuma seção inválida',
|
|
843
|
+
'crud.authoring.health.pendingEmpty': 'Nenhuma seção pendente',
|
|
844
|
+
'crud.authoring.health.readyEmpty': 'Nenhuma seção pronta ainda',
|
|
845
|
+
'crud.authoring.diagnostics.errors': 'Erros',
|
|
846
|
+
'crud.authoring.diagnostics.warnings': 'Alertas',
|
|
847
|
+
'crud.authoring.diagnostics.errorSummary': '{count} diagnósticos bloqueantes nesta seção.',
|
|
848
|
+
'crud.authoring.diagnostics.warningSummary': '{count} diagnósticos que valem revisão nesta seção.',
|
|
849
|
+
'crud.authoring.connection.resourcePath': 'Recurso',
|
|
850
|
+
'crud.authoring.connection.resourcePath.placeholder': 'ex.: api/human-resources/cargos',
|
|
851
|
+
'crud.authoring.connection.idField': 'Campo identificador',
|
|
852
|
+
'crud.authoring.connection.idField.placeholder': 'ex.: id',
|
|
853
|
+
'crud.authoring.connection.endpointKey': 'Endpoint/API',
|
|
854
|
+
'crud.authoring.defaults.openMode': 'Modo de abertura padrão',
|
|
855
|
+
'crud.authoring.defaults.header.showBack': 'Exibir botão Voltar',
|
|
856
|
+
'crud.authoring.defaults.header.backLabel': 'Texto do botão Voltar',
|
|
857
|
+
'crud.authoring.defaults.header.variant': 'Variante do botão',
|
|
858
|
+
'crud.authoring.defaults.header.sticky': 'Cabeçalho fixo',
|
|
859
|
+
'crud.authoring.defaults.header.breadcrumbs': 'Exibir breadcrumbs',
|
|
860
|
+
'crud.authoring.defaults.header.divider': 'Exibir divisor inferior',
|
|
861
|
+
'crud.authoring.defaults.modal.group': 'Apresentação do modal',
|
|
862
|
+
'crud.authoring.defaults.modal.density': 'Densidade do modal',
|
|
863
|
+
'crud.authoring.defaults.modal.density.default': 'Padrão',
|
|
864
|
+
'crud.authoring.defaults.modal.density.compact': 'Compacta',
|
|
865
|
+
'crud.authoring.defaults.modal.fullscreenBreakpoint': 'Breakpoint para fullscreen',
|
|
866
|
+
'crud.authoring.defaults.modal.canMaximize': 'Exibir controle de maximizar',
|
|
867
|
+
'crud.authoring.defaults.modal.startMaximized': 'Iniciar maximizado',
|
|
868
|
+
'crud.authoring.defaults.modal.rememberLastState': 'Lembrar o último tamanho do modal',
|
|
869
|
+
'crud.authoring.defaults.modal.disableCloseOnEsc': 'Bloquear fechamento por Escape',
|
|
870
|
+
'crud.authoring.defaults.modal.disableCloseOnBackdrop': 'Bloquear fechamento por backdrop',
|
|
871
|
+
'crud.authoring.defaults.modal.noteDensity': 'Densidade: {value}',
|
|
872
|
+
'crud.authoring.defaults.modal.noteNoMaximize': 'Maximizar oculto',
|
|
873
|
+
'crud.authoring.defaults.modal.noteStartMaximized': 'Abre maximizado',
|
|
874
|
+
'crud.authoring.defaults.modal.noteRememberState': 'Lembra o último tamanho',
|
|
875
|
+
'crud.authoring.defaults.modal.noteEscLocked': 'Escape bloqueado',
|
|
876
|
+
'crud.authoring.defaults.modal.noteBackdropLocked': 'Backdrop bloqueado',
|
|
877
|
+
'crud.authoring.defaults.modal.noteBreakpoint': 'Fullscreen em {value}px',
|
|
878
|
+
'crud.authoring.defaults.back.group': 'Comportamento de voltar',
|
|
879
|
+
'crud.authoring.defaults.back.strategy': 'Estratégia de voltar',
|
|
880
|
+
'crud.authoring.defaults.back.strategy.auto': 'Auto',
|
|
881
|
+
'crud.authoring.defaults.back.strategy.close': 'Fechar',
|
|
882
|
+
'crud.authoring.defaults.back.strategy.navigate': 'Navegar',
|
|
883
|
+
'crud.authoring.defaults.back.returnTo': 'Rota de retorno',
|
|
884
|
+
'crud.authoring.defaults.back.confirmOnDirty': 'Confirmar ao sair de formulários alterados',
|
|
885
|
+
'crud.authoring.defaults.back.noteStrategy': 'Estratégia: {value}',
|
|
886
|
+
'crud.authoring.defaults.back.noteReturnTo': 'Retorno: {value}',
|
|
887
|
+
'crud.authoring.defaults.back.noteConfirmOnDirty': 'Confirmação ao sair',
|
|
888
|
+
'crud.authoring.action.mode': 'Modo de abertura',
|
|
889
|
+
'crud.authoring.action.route': 'Rota',
|
|
890
|
+
'crud.authoring.action.route.placeholder': 'ex.: /cargos/view/:id',
|
|
891
|
+
'crud.authoring.action.formId': 'formId',
|
|
892
|
+
'crud.authoring.action.formId.placeholder': 'ex.: cargos-edit',
|
|
893
|
+
'crud.authoring.action.schemaUrl': 'Schema URL',
|
|
894
|
+
'crud.authoring.action.submitUrl': 'Submit URL',
|
|
895
|
+
'crud.authoring.action.submitMethod': 'Método de submit',
|
|
896
|
+
'crud.authoring.action.apiEndpointKey': 'API endpoint key',
|
|
897
|
+
'crud.authoring.action.apiUrlEntry': 'API URL entry',
|
|
898
|
+
'crud.authoring.action.advanced': 'Detalhes de submit e API',
|
|
899
|
+
'crud.authoring.action.bindingGroup': 'Vínculo',
|
|
900
|
+
'crud.authoring.action.schemaGroup': 'Contrato de schema',
|
|
901
|
+
'crud.authoring.action.submitGroup': 'Contrato de submit',
|
|
902
|
+
'crud.authoring.action.apiGroup': 'Mapeamento de API',
|
|
903
|
+
'crud.authoring.action.inputsGroup': 'Seed e mapeamento de inputs',
|
|
904
|
+
'crud.authoring.action.inputsNote': 'Opcional: use Parâmetros para valores derivados da linha e Valor inicial apenas para seed fixo do formulário.',
|
|
905
|
+
'crud.authoring.action.paramsSubgroup': 'Mapeamentos derivados da linha',
|
|
906
|
+
'crud.authoring.action.initialValueSubgroup': 'Seed fixo do formulário',
|
|
907
|
+
'crud.authoring.action.backGroup': 'Override do comportamento de voltar',
|
|
908
|
+
'crud.authoring.action.backStrategy': 'Override da estratégia de voltar',
|
|
909
|
+
'crud.authoring.action.backReturnTo': 'Rota de retorno da ação',
|
|
910
|
+
'crud.authoring.action.backConfirmOnDirty': 'Confirmar ao sair de formulários alterados',
|
|
911
|
+
'crud.authoring.action.backUseDefaults': 'Usar defaults do CRUD',
|
|
912
|
+
'crud.authoring.action.backUsesDefaults': 'Usando os defaults do CRUD para voltar',
|
|
913
|
+
'crud.authoring.action.backNoteStrategy': 'Estratégia: {value}',
|
|
914
|
+
'crud.authoring.action.backNoteReturnTo': 'Retorno: {value}',
|
|
915
|
+
'crud.authoring.action.backNoteConfirmOnDirty': 'Confirmação ao sair',
|
|
916
|
+
'crud.authoring.action.paramFrom': 'Campo de origem',
|
|
917
|
+
'crud.authoring.action.paramTo': 'Destino',
|
|
918
|
+
'crud.authoring.action.paramName': 'Nome no destino',
|
|
919
|
+
'crud.authoring.action.paramAdd': 'Adicionar mapeamento',
|
|
920
|
+
'crud.authoring.action.paramRemove': 'Remover mapeamento',
|
|
921
|
+
'crud.authoring.action.paramTarget.routeParam': 'Parâmetro de rota',
|
|
922
|
+
'crud.authoring.action.paramTarget.query': 'Query string',
|
|
923
|
+
'crud.authoring.action.paramTarget.input': 'Input do formulário',
|
|
924
|
+
'crud.authoring.action.initialValue': 'Seed inicial do formulário (JSON)',
|
|
925
|
+
'crud.authoring.action.paramsSummary': '{count} mapeamentos',
|
|
926
|
+
'crud.authoring.action.paramsSummaryCompact': '{count} map.',
|
|
927
|
+
'crud.authoring.action.paramsNone': 'Nenhum mapeamento de parâmetro ainda',
|
|
928
|
+
'crud.authoring.action.paramsNoteEmpty': 'Mapeie campos da linha atual para parâmetros de rota, query string ou inputs do formulário.',
|
|
929
|
+
'crud.authoring.action.paramsNoteConfigured': '{count} mapeamentos para {targets}.',
|
|
930
|
+
'crud.authoring.action.paramsNoteReady': 'Mapeamentos prontos',
|
|
931
|
+
'crud.authoring.action.initialValueReady': 'Seed fixo do formulário configurado',
|
|
932
|
+
'crud.authoring.action.initialValueReadyCompact': 'Seed',
|
|
933
|
+
'crud.authoring.action.initialValueEmpty': 'Nenhum seed fixo do formulário ainda',
|
|
934
|
+
'crud.authoring.action.initialValueInvalid': 'O JSON do seed inicial ainda não é válido',
|
|
935
|
+
'crud.authoring.action.initialValueNoteConfigured': 'Objeto injetado em inputs.initialValue antes da abertura do formulário.',
|
|
936
|
+
'crud.authoring.action.initialValueNoteReady': 'Seed fixo pronto',
|
|
937
|
+
'crud.authoring.action.initialValueNoteEmpty': 'Use isso apenas para valores fixos que não vêm da linha selecionada.',
|
|
938
|
+
'crud.authoring.action.binding': 'Vínculo',
|
|
939
|
+
'crud.authoring.action.bindingMissing': 'Vínculo ainda não configurado',
|
|
940
|
+
'crud.authoring.action.schema': 'Schema',
|
|
941
|
+
'crud.authoring.action.schemaPending': 'Contrato de schema ainda não configurado',
|
|
942
|
+
'crud.authoring.action.api': 'API',
|
|
943
|
+
'crud.authoring.action.apiEntryReady': 'API URL entry customizado configurado',
|
|
944
|
+
'crud.authoring.action.apiPending': 'Mapeamento de API ainda não configurado',
|
|
945
|
+
'crud.authoring.action.groupsInvalid': 'Inválido: {groups}',
|
|
946
|
+
'crud.authoring.action.groupsPending': 'Pendente: {groups}',
|
|
947
|
+
'crud.authoring.action.groupsReady': 'Todos os grupos prontos',
|
|
948
|
+
'crud.authoring.action.primaryInvalid': 'Resolver: {groups}',
|
|
949
|
+
'crud.authoring.action.primaryPending': 'Próximo: {groups}',
|
|
950
|
+
'crud.authoring.action.primaryReady': 'Núcleo pronto',
|
|
951
|
+
'crud.authoring.action.advancedPending': 'Pendente: {groups}',
|
|
952
|
+
'crud.authoring.action.advancedReady': 'Avançado pronto',
|
|
953
|
+
'crud.authoring.action.editorialInvalid': 'Resolver: {groups}',
|
|
954
|
+
'crud.authoring.action.editorialPending': 'Fechar: {groups}',
|
|
955
|
+
'crud.authoring.action.editorialReady': 'Pronta para uso',
|
|
956
|
+
'crud.authoring.action.submitPending': 'Detalhes de submit/API ainda não configurados',
|
|
957
|
+
'crud.authoring.action.status.ready': 'Pronta',
|
|
958
|
+
'crud.authoring.action.status.pending': 'Pendente',
|
|
959
|
+
'crud.authoring.action.status.invalid': 'Inválida',
|
|
960
|
+
'crud.authoring.mode.route': 'Rota',
|
|
961
|
+
'crud.authoring.mode.modal': 'Modal',
|
|
962
|
+
'crud.authoring.mode.drawer': 'Drawer',
|
|
963
|
+
'crud.authoring.header.variant.ghost': 'Ghost',
|
|
964
|
+
'crud.authoring.header.variant.tonal': 'Tonal',
|
|
965
|
+
'crud.authoring.header.variant.outlined': 'Outlined',
|
|
966
|
+
'crud.authoring.submitMethod.post': 'POST',
|
|
967
|
+
'crud.authoring.submitMethod.put': 'PUT',
|
|
968
|
+
'crud.authoring.submitMethod.patch': 'PATCH',
|
|
969
|
+
'crud.authoring.action.create': 'Criar',
|
|
970
|
+
'crud.authoring.action.view': 'Visualizar',
|
|
971
|
+
'crud.authoring.action.edit': 'Editar',
|
|
972
|
+
'crud.authoring.table.summary': 'A configuração detalhada da tabela continua sendo tratada pela superfície canônica de TableConfig. Nesta fase, o editor de CRUD preserva o snapshot atual da tabela sem duplicar o editor completo.',
|
|
973
|
+
'crud.authoring.json.hint': 'O JSON abaixo representa o documento canônico de authoring do CRUD.',
|
|
974
|
+
'crud.authoring.table.structureSummary': '{count} colunas | {density}',
|
|
975
|
+
'crud.authoring.table.paginationSummary': 'Página com {value}',
|
|
976
|
+
'crud.authoring.table.paginationOff': 'Paginação desativada',
|
|
977
|
+
'crud.authoring.table.sortingClient': 'Ordenação client-side',
|
|
978
|
+
'crud.authoring.table.sortingServer': 'Ordenação server-side',
|
|
979
|
+
'crud.authoring.table.sortingOff': 'Ordenação desativada',
|
|
980
|
+
'crud.authoring.table.statesDefault': 'Textos padrão',
|
|
981
|
+
'crud.authoring.table.statesCustom': '{count} estados customizados',
|
|
982
|
+
'crud.authoring.table.sectionSummary': '{structure}. {behavior}. {states}.',
|
|
983
|
+
'crud.authoring.table.flowSummary': 'A apresentação principal continua visível abaixo. Paginação, ordenação, textos de estado e colunas ficam nos painéis avançados da tabela.',
|
|
984
|
+
'crud.authoring.table.density.compact': 'Compacta',
|
|
985
|
+
'crud.authoring.table.density.comfortable': 'Confortável',
|
|
986
|
+
'crud.authoring.table.density.spacious': 'Espaçada',
|
|
987
|
+
'crud.authoring.json.summaryErrors': 'O documento canônico ainda possui erros de validação.',
|
|
988
|
+
'crud.authoring.json.summaryWarnings': 'O documento canônico possui diagnósticos que valem revisão.',
|
|
989
|
+
'crud.authoring.json.summaryClean': 'O documento canônico está estruturalmente consistente.',
|
|
990
|
+
'crud.authoring.validation.routeRequired': 'Informe uma rota quando o modo efetivo for route.',
|
|
991
|
+
'crud.authoring.validation.formIdRequired': 'Informe um formId quando o modo efetivo for modal ou drawer.',
|
|
992
|
+
'crud.authoring.validation.submitContract': 'submitUrl e submitMethod devem ser informados juntos.',
|
|
993
|
+
'crud.authoring.overview.actionsNextCompact': 'Próxima: {action}. {summary}',
|
|
994
|
+
'crud.authoring.action.focusInvalid': '{count} grupos bloqueando',
|
|
995
|
+
'crud.authoring.action.focusPending': '{count} grupos pendentes',
|
|
996
|
+
'crud.authoring.action.focusReady': 'Pronta',
|
|
997
|
+
'crud.authoring.action.headerReady': 'Pronta',
|
|
998
|
+
'crud.authoring.action.bindingReadyNote': 'Vínculo configurado',
|
|
999
|
+
'crud.authoring.action.schemaReadyNote': 'Schema vinculado',
|
|
1000
|
+
'crud.authoring.action.submitReadyNote': 'Contrato de submit pronto',
|
|
1001
|
+
'crud.authoring.action.apiReadyNote': 'Mapeamento de API pronto',
|
|
1002
|
+
},
|
|
1003
|
+
'en-US': {
|
|
1004
|
+
'crud.authoring.title': 'CRUD settings',
|
|
1005
|
+
'crud.authoring.subtitle': 'Edit the canonical CRUD flow document without shifting semantics to the host.',
|
|
1006
|
+
'crud.authoring.section.connection': 'Connection',
|
|
1007
|
+
'crud.authoring.section.actions': 'Actions',
|
|
1008
|
+
'crud.authoring.section.defaults': 'Open mode and header',
|
|
1009
|
+
'crud.authoring.section.table': 'Table',
|
|
1010
|
+
'crud.authoring.section.json': 'JSON',
|
|
1011
|
+
'crud.authoring.overview.connection': 'Connection',
|
|
1012
|
+
'crud.authoring.overview.defaults': 'Defaults',
|
|
1013
|
+
'crud.authoring.overview.actions': 'Actions',
|
|
1014
|
+
'crud.authoring.overview.table': 'Table',
|
|
1015
|
+
'crud.authoring.overview.connectionMissing': 'Resource not configured yet',
|
|
1016
|
+
'crud.authoring.overview.connectionIdField': 'ID: {value}',
|
|
1017
|
+
'crud.authoring.overview.connectionEndpoint': 'API: {value}',
|
|
1018
|
+
'crud.authoring.overview.connectionDetailsPending': 'Identifier and endpoint details are still optional here.',
|
|
1019
|
+
'crud.authoring.overview.backVisible': 'Back visible',
|
|
1020
|
+
'crud.authoring.overview.backHidden': 'Back hidden',
|
|
1021
|
+
'crud.authoring.overview.modalDensity': 'Modal: {value}',
|
|
1022
|
+
'crud.authoring.overview.backStrategy': 'Back: {value}',
|
|
1023
|
+
'crud.authoring.overview.modalRememberState': 'State remembered',
|
|
1024
|
+
'crud.authoring.overview.backConfirmOnDirty': 'Dirty confirm',
|
|
1025
|
+
'crud.authoring.overview.headerSticky': 'Sticky header',
|
|
1026
|
+
'crud.authoring.overview.headerBreadcrumbs': 'Breadcrumbs',
|
|
1027
|
+
'crud.authoring.overview.columns': 'columns',
|
|
1028
|
+
'crud.authoring.overview.columnsCount': '{count} columns',
|
|
1029
|
+
'crud.authoring.overview.validation': 'Validation',
|
|
1030
|
+
'crud.authoring.overview.validationErrors': '{count} errors',
|
|
1031
|
+
'crud.authoring.overview.validationWarnings': '{count} warnings',
|
|
1032
|
+
'crud.authoring.overview.validationClean': 'No blocking diagnostics',
|
|
1033
|
+
'crud.authoring.overview.validationGrouped': 'Grouped by section below for faster troubleshooting.',
|
|
1034
|
+
'crud.authoring.overview.validationAligned': 'Shell guidance and diagnostics are aligned.',
|
|
1035
|
+
'crud.authoring.overview.actionsReady': '{count} ready',
|
|
1036
|
+
'crud.authoring.overview.actionsPending': '{count} pending',
|
|
1037
|
+
'crud.authoring.overview.actionsInvalid': '{count} invalid',
|
|
1038
|
+
'crud.authoring.overview.actionsTracked': '{count} tracked action blocks',
|
|
1039
|
+
'crud.authoring.overview.actionsNext': 'Next: {action}. {summary}',
|
|
1040
|
+
'crud.authoring.section.status.ready': 'Ready',
|
|
1041
|
+
'crud.authoring.section.status.pending': 'Needs attention',
|
|
1042
|
+
'crud.authoring.section.status.invalid': 'Invalid',
|
|
1043
|
+
'crud.authoring.section.actions.invalidSummary': '{count} action blocks have validation errors.',
|
|
1044
|
+
'crud.authoring.section.actions.readySummary': 'All primary CRUD actions are configured.',
|
|
1045
|
+
'crud.authoring.section.actions.pendingSummary': '{count} of {total} action blocks are ready.',
|
|
1046
|
+
'crud.authoring.section.actions.invalidSummaryDetailed': '{count} action blocks have validation errors. Start with {action}.',
|
|
1047
|
+
'crud.authoring.section.actions.pendingSummaryDetailed': '{count} of {total} action blocks are ready. Next: {action}.',
|
|
1048
|
+
'crud.authoring.nextFocus.connection': 'Next focus: connection',
|
|
1049
|
+
'crud.authoring.nextFocus.defaults': 'Next focus: defaults',
|
|
1050
|
+
'crud.authoring.nextFocus.actions': 'Next focus: actions',
|
|
1051
|
+
'crud.authoring.nextFocus.table': 'Next focus: table',
|
|
1052
|
+
'crud.authoring.nextFocus.invalid': 'Resolve the blocking diagnostics in this section before saving with confidence.',
|
|
1053
|
+
'crud.authoring.nextFocus.pending': 'This section is the next best place to complete the canonical CRUD flow.',
|
|
1054
|
+
'crud.authoring.nextFocus.actionsInvalid': 'Resolve the blocking diagnostics in {action}. {summary}.',
|
|
1055
|
+
'crud.authoring.nextFocus.actionsPending': 'Complete {action} next. {summary}.',
|
|
1056
|
+
'crud.authoring.nextFocus.ready': 'All tracked sections are currently in a good state.',
|
|
1057
|
+
'crud.authoring.nextFocus.complete': 'Complete',
|
|
1058
|
+
'crud.authoring.nextFocus.completeTitle': 'Shell guidance complete',
|
|
1059
|
+
'crud.authoring.health.invalid': 'Invalid',
|
|
1060
|
+
'crud.authoring.health.pending': 'Needs attention',
|
|
1061
|
+
'crud.authoring.health.ready': 'Healthy',
|
|
1062
|
+
'crud.authoring.health.invalidEmpty': 'No invalid sections',
|
|
1063
|
+
'crud.authoring.health.pendingEmpty': 'No pending sections',
|
|
1064
|
+
'crud.authoring.health.readyEmpty': 'No ready sections yet',
|
|
1065
|
+
'crud.authoring.diagnostics.errors': 'Errors',
|
|
1066
|
+
'crud.authoring.diagnostics.warnings': 'Warnings',
|
|
1067
|
+
'crud.authoring.diagnostics.errorSummary': '{count} blocking diagnostics in this section.',
|
|
1068
|
+
'crud.authoring.diagnostics.warningSummary': '{count} diagnostics worth reviewing in this section.',
|
|
1069
|
+
'crud.authoring.connection.resourcePath': 'Resource',
|
|
1070
|
+
'crud.authoring.connection.resourcePath.placeholder': 'e.g. api/human-resources/cargos',
|
|
1071
|
+
'crud.authoring.connection.idField': 'Identifier field',
|
|
1072
|
+
'crud.authoring.connection.idField.placeholder': 'e.g. id',
|
|
1073
|
+
'crud.authoring.connection.endpointKey': 'Endpoint/API',
|
|
1074
|
+
'crud.authoring.defaults.openMode': 'Default open mode',
|
|
1075
|
+
'crud.authoring.defaults.header.showBack': 'Show back button',
|
|
1076
|
+
'crud.authoring.defaults.header.backLabel': 'Back button label',
|
|
1077
|
+
'crud.authoring.defaults.header.variant': 'Button variant',
|
|
1078
|
+
'crud.authoring.defaults.header.sticky': 'Sticky header',
|
|
1079
|
+
'crud.authoring.defaults.header.breadcrumbs': 'Show breadcrumbs',
|
|
1080
|
+
'crud.authoring.defaults.header.divider': 'Show bottom divider',
|
|
1081
|
+
'crud.authoring.defaults.modal.group': 'Modal presentation',
|
|
1082
|
+
'crud.authoring.defaults.modal.density': 'Modal density',
|
|
1083
|
+
'crud.authoring.defaults.modal.density.default': 'Default',
|
|
1084
|
+
'crud.authoring.defaults.modal.density.compact': 'Compact',
|
|
1085
|
+
'crud.authoring.defaults.modal.fullscreenBreakpoint': 'Fullscreen breakpoint',
|
|
1086
|
+
'crud.authoring.defaults.modal.canMaximize': 'Show maximize control',
|
|
1087
|
+
'crud.authoring.defaults.modal.startMaximized': 'Start maximized',
|
|
1088
|
+
'crud.authoring.defaults.modal.rememberLastState': 'Remember last modal size',
|
|
1089
|
+
'crud.authoring.defaults.modal.disableCloseOnEsc': 'Block close on Escape',
|
|
1090
|
+
'crud.authoring.defaults.modal.disableCloseOnBackdrop': 'Block close on backdrop',
|
|
1091
|
+
'crud.authoring.defaults.modal.noteDensity': 'Density: {value}',
|
|
1092
|
+
'crud.authoring.defaults.modal.noteNoMaximize': 'Maximize hidden',
|
|
1093
|
+
'crud.authoring.defaults.modal.noteStartMaximized': 'Starts maximized',
|
|
1094
|
+
'crud.authoring.defaults.modal.noteRememberState': 'Last size remembered',
|
|
1095
|
+
'crud.authoring.defaults.modal.noteEscLocked': 'Escape locked',
|
|
1096
|
+
'crud.authoring.defaults.modal.noteBackdropLocked': 'Backdrop locked',
|
|
1097
|
+
'crud.authoring.defaults.modal.noteBreakpoint': 'Fullscreen at {value}px',
|
|
1098
|
+
'crud.authoring.defaults.back.group': 'Back behavior',
|
|
1099
|
+
'crud.authoring.defaults.back.strategy': 'Back strategy',
|
|
1100
|
+
'crud.authoring.defaults.back.strategy.auto': 'Auto',
|
|
1101
|
+
'crud.authoring.defaults.back.strategy.close': 'Close',
|
|
1102
|
+
'crud.authoring.defaults.back.strategy.navigate': 'Navigate',
|
|
1103
|
+
'crud.authoring.defaults.back.returnTo': 'Return route',
|
|
1104
|
+
'crud.authoring.defaults.back.confirmOnDirty': 'Confirm when leaving dirty forms',
|
|
1105
|
+
'crud.authoring.defaults.back.noteStrategy': 'Strategy: {value}',
|
|
1106
|
+
'crud.authoring.defaults.back.noteReturnTo': 'Return: {value}',
|
|
1107
|
+
'crud.authoring.defaults.back.noteConfirmOnDirty': 'Dirty confirmation',
|
|
1108
|
+
'crud.authoring.action.mode': 'Open mode',
|
|
1109
|
+
'crud.authoring.action.route': 'Route',
|
|
1110
|
+
'crud.authoring.action.route.placeholder': 'e.g. /cargos/view/:id',
|
|
1111
|
+
'crud.authoring.action.formId': 'formId',
|
|
1112
|
+
'crud.authoring.action.formId.placeholder': 'e.g. cargos-edit',
|
|
1113
|
+
'crud.authoring.action.schemaUrl': 'Schema URL',
|
|
1114
|
+
'crud.authoring.action.submitUrl': 'Submit URL',
|
|
1115
|
+
'crud.authoring.action.submitMethod': 'Submit method',
|
|
1116
|
+
'crud.authoring.action.apiEndpointKey': 'API endpoint key',
|
|
1117
|
+
'crud.authoring.action.apiUrlEntry': 'API URL entry',
|
|
1118
|
+
'crud.authoring.action.advanced': 'Submit and API details',
|
|
1119
|
+
'crud.authoring.action.bindingGroup': 'Binding',
|
|
1120
|
+
'crud.authoring.action.schemaGroup': 'Schema contract',
|
|
1121
|
+
'crud.authoring.action.submitGroup': 'Submit contract',
|
|
1122
|
+
'crud.authoring.action.apiGroup': 'API mapping',
|
|
1123
|
+
'crud.authoring.action.inputsGroup': 'Input seeding',
|
|
1124
|
+
'crud.authoring.action.inputsNote': 'Optional: use Params for row-derived values and Initial value only for fixed form seed.',
|
|
1125
|
+
'crud.authoring.action.paramsSubgroup': 'Row-derived mappings',
|
|
1126
|
+
'crud.authoring.action.initialValueSubgroup': 'Fixed form seed',
|
|
1127
|
+
'crud.authoring.action.backGroup': 'Back behavior override',
|
|
1128
|
+
'crud.authoring.action.backStrategy': 'Back strategy override',
|
|
1129
|
+
'crud.authoring.action.backReturnTo': 'Action return route',
|
|
1130
|
+
'crud.authoring.action.backConfirmOnDirty': 'Confirm when leaving dirty forms',
|
|
1131
|
+
'crud.authoring.action.backUseDefaults': 'Use defaults',
|
|
1132
|
+
'crud.authoring.action.backUsesDefaults': 'Using CRUD defaults for back behavior',
|
|
1133
|
+
'crud.authoring.action.backNoteStrategy': 'Strategy: {value}',
|
|
1134
|
+
'crud.authoring.action.backNoteReturnTo': 'Return: {value}',
|
|
1135
|
+
'crud.authoring.action.backNoteConfirmOnDirty': 'Dirty confirmation',
|
|
1136
|
+
'crud.authoring.action.paramFrom': 'From field',
|
|
1137
|
+
'crud.authoring.action.paramTo': 'Target',
|
|
1138
|
+
'crud.authoring.action.paramName': 'Destination name',
|
|
1139
|
+
'crud.authoring.action.paramAdd': 'Add mapping',
|
|
1140
|
+
'crud.authoring.action.paramRemove': 'Remove mapping',
|
|
1141
|
+
'crud.authoring.action.paramTarget.routeParam': 'Route param',
|
|
1142
|
+
'crud.authoring.action.paramTarget.query': 'Query string',
|
|
1143
|
+
'crud.authoring.action.paramTarget.input': 'Form input',
|
|
1144
|
+
'crud.authoring.action.initialValue': 'Initial form seed (JSON)',
|
|
1145
|
+
'crud.authoring.action.paramsSummary': '{count} mappings',
|
|
1146
|
+
'crud.authoring.action.paramsSummaryCompact': '{count} map',
|
|
1147
|
+
'crud.authoring.action.paramsNone': 'No param mappings yet',
|
|
1148
|
+
'crud.authoring.action.paramsNoteEmpty': 'Map fields from the current row into route params, query string, or dialog inputs.',
|
|
1149
|
+
'crud.authoring.action.paramsNoteConfigured': '{count} mappings into {targets}.',
|
|
1150
|
+
'crud.authoring.action.paramsNoteReady': 'Mappings ready',
|
|
1151
|
+
'crud.authoring.action.initialValueReady': 'Initial value seed configured',
|
|
1152
|
+
'crud.authoring.action.initialValueReadyCompact': 'Seed',
|
|
1153
|
+
'crud.authoring.action.initialValueEmpty': 'No fixed form seed yet',
|
|
1154
|
+
'crud.authoring.action.initialValueInvalid': 'Initial value JSON is not valid yet',
|
|
1155
|
+
'crud.authoring.action.initialValueNoteConfigured': 'Seeded object injected into inputs.initialValue before form open.',
|
|
1156
|
+
'crud.authoring.action.initialValueNoteReady': 'Fixed seed ready',
|
|
1157
|
+
'crud.authoring.action.initialValueNoteEmpty': 'Use this only for fixed seed values that do not come from the selected row.',
|
|
1158
|
+
'crud.authoring.action.binding': 'Binding',
|
|
1159
|
+
'crud.authoring.action.bindingMissing': 'Binding not configured yet',
|
|
1160
|
+
'crud.authoring.action.schema': 'Schema',
|
|
1161
|
+
'crud.authoring.action.schemaPending': 'Schema contract not configured yet',
|
|
1162
|
+
'crud.authoring.action.api': 'API',
|
|
1163
|
+
'crud.authoring.action.apiEntryReady': 'Custom API URL entry configured',
|
|
1164
|
+
'crud.authoring.action.apiPending': 'API mapping not configured yet',
|
|
1165
|
+
'crud.authoring.action.groupsInvalid': 'Invalid: {groups}',
|
|
1166
|
+
'crud.authoring.action.groupsPending': 'Pending: {groups}',
|
|
1167
|
+
'crud.authoring.action.groupsReady': 'All groups ready',
|
|
1168
|
+
'crud.authoring.action.primaryInvalid': 'Resolve: {groups}',
|
|
1169
|
+
'crud.authoring.action.primaryPending': 'Next: {groups}',
|
|
1170
|
+
'crud.authoring.action.primaryReady': 'Core ready',
|
|
1171
|
+
'crud.authoring.action.advancedPending': 'Pending: {groups}',
|
|
1172
|
+
'crud.authoring.action.advancedReady': 'Advanced ready',
|
|
1173
|
+
'crud.authoring.action.editorialInvalid': 'Resolve: {groups}',
|
|
1174
|
+
'crud.authoring.action.editorialPending': 'Finish: {groups}',
|
|
1175
|
+
'crud.authoring.action.editorialReady': 'Ready to use',
|
|
1176
|
+
'crud.authoring.action.submitPending': 'Submit/API details not configured yet',
|
|
1177
|
+
'crud.authoring.action.status.ready': 'Ready',
|
|
1178
|
+
'crud.authoring.action.status.pending': 'Pending',
|
|
1179
|
+
'crud.authoring.action.status.invalid': 'Invalid',
|
|
1180
|
+
'crud.authoring.mode.route': 'Route',
|
|
1181
|
+
'crud.authoring.mode.modal': 'Modal',
|
|
1182
|
+
'crud.authoring.mode.drawer': 'Drawer',
|
|
1183
|
+
'crud.authoring.header.variant.ghost': 'Ghost',
|
|
1184
|
+
'crud.authoring.header.variant.tonal': 'Tonal',
|
|
1185
|
+
'crud.authoring.header.variant.outlined': 'Outlined',
|
|
1186
|
+
'crud.authoring.submitMethod.post': 'POST',
|
|
1187
|
+
'crud.authoring.submitMethod.put': 'PUT',
|
|
1188
|
+
'crud.authoring.submitMethod.patch': 'PATCH',
|
|
1189
|
+
'crud.authoring.action.create': 'Create',
|
|
1190
|
+
'crud.authoring.action.view': 'View',
|
|
1191
|
+
'crud.authoring.action.edit': 'Edit',
|
|
1192
|
+
'crud.authoring.table.summary': 'Detailed table configuration remains owned by the canonical TableConfig surface. In this phase, the CRUD editor preserves the current table snapshot without duplicating the full table editor.',
|
|
1193
|
+
'crud.authoring.table.structureSummary': '{count} columns | {density}',
|
|
1194
|
+
'crud.authoring.table.paginationSummary': 'Page size {value}',
|
|
1195
|
+
'crud.authoring.table.paginationOff': 'Pagination off',
|
|
1196
|
+
'crud.authoring.table.sortingClient': 'Client sorting',
|
|
1197
|
+
'crud.authoring.table.sortingServer': 'Server sorting',
|
|
1198
|
+
'crud.authoring.table.sortingOff': 'Sorting off',
|
|
1199
|
+
'crud.authoring.table.statesDefault': 'Default state copy',
|
|
1200
|
+
'crud.authoring.table.statesCustom': '{count} custom states',
|
|
1201
|
+
'crud.authoring.table.sectionSummary': '{structure}. {behavior}. {states}.',
|
|
1202
|
+
'crud.authoring.table.flowSummary': 'Core presentation stays visible below. Pagination, sorting, state copy, and columns stay in advanced table panels.',
|
|
1203
|
+
'crud.authoring.table.density.compact': 'Compact',
|
|
1204
|
+
'crud.authoring.table.density.comfortable': 'Comfortable',
|
|
1205
|
+
'crud.authoring.table.density.spacious': 'Spacious',
|
|
1206
|
+
'crud.authoring.json.hint': 'The JSON below represents the canonical CRUD authoring document.',
|
|
1207
|
+
'crud.authoring.json.summaryErrors': 'The canonical document still has validation errors.',
|
|
1208
|
+
'crud.authoring.json.summaryWarnings': 'The canonical document has diagnostics worth reviewing.',
|
|
1209
|
+
'crud.authoring.json.summaryClean': 'The canonical document is structurally consistent.',
|
|
1210
|
+
'crud.authoring.validation.routeRequired': 'Provide a route when the effective mode is route.',
|
|
1211
|
+
'crud.authoring.validation.formIdRequired': 'Provide a formId when the effective mode is modal or drawer.',
|
|
1212
|
+
'crud.authoring.validation.submitContract': 'submitUrl and submitMethod must be provided together.',
|
|
1213
|
+
'crud.authoring.overview.actionsNextCompact': 'Next: {action}. {summary}',
|
|
1214
|
+
'crud.authoring.action.focusInvalid': '{count} blocking groups',
|
|
1215
|
+
'crud.authoring.action.focusPending': '{count} groups still pending',
|
|
1216
|
+
'crud.authoring.action.focusReady': 'Ready',
|
|
1217
|
+
'crud.authoring.action.headerReady': 'Ready',
|
|
1218
|
+
'crud.authoring.action.bindingReadyNote': 'Binding configured',
|
|
1219
|
+
'crud.authoring.action.schemaReadyNote': 'Schema linked',
|
|
1220
|
+
'crud.authoring.action.submitReadyNote': 'Submit contract ready',
|
|
1221
|
+
'crud.authoring.action.apiReadyNote': 'API mapping ready',
|
|
1222
|
+
},
|
|
1223
|
+
},
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
function translateCrudAuthoringText(i18n, key, fallback, params) {
|
|
1227
|
+
return i18n.t(key, params, fallback, PRAXIS_CRUD_AUTHORING_I18N_NAMESPACE);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
const ACTION_KEYS = ['create', 'view', 'edit'];
|
|
1231
|
+
const OPEN_MODES = ['route', 'modal', 'drawer'];
|
|
1232
|
+
const HEADER_VARIANTS = ['ghost', 'tonal', 'outlined'];
|
|
1233
|
+
const SUBMIT_METHODS = ['post', 'put', 'patch'];
|
|
1234
|
+
const MODAL_DENSITIES = ['default', 'compact'];
|
|
1235
|
+
const BACK_STRATEGIES = ['auto', 'close', 'navigate'];
|
|
1236
|
+
const PARAM_TARGETS = ['routeParam', 'query', 'input'];
|
|
1237
|
+
class CrudMetadataEditorComponent {
|
|
1238
|
+
documentInput = input(null, ...(ngDevMode ? [{ debugName: "documentInput", alias: 'document' }] : [{ alias: 'document' }]));
|
|
1239
|
+
metadataInput = input(null, ...(ngDevMode ? [{ debugName: "metadataInput", alias: 'metadata' }] : [{ alias: 'metadata' }]));
|
|
1240
|
+
crudIdInput = input(null, ...(ngDevMode ? [{ debugName: "crudIdInput", alias: 'crudId' }] : [{ alias: 'crudId' }]));
|
|
1241
|
+
readonlyInput = input(false, ...(ngDevMode ? [{ debugName: "readonlyInput", alias: 'readonly' }] : [{ alias: 'readonly' }]));
|
|
1242
|
+
isDirty$ = new BehaviorSubject(false);
|
|
1243
|
+
isValid$ = new BehaviorSubject(true);
|
|
1244
|
+
isBusy$ = new BehaviorSubject(false);
|
|
1245
|
+
actionKeys = ACTION_KEYS;
|
|
1246
|
+
healthBuckets = ['invalid', 'pending', 'ready'];
|
|
1247
|
+
openModes = OPEN_MODES;
|
|
1248
|
+
headerVariants = HEADER_VARIANTS;
|
|
1249
|
+
submitMethods = SUBMIT_METHODS;
|
|
1250
|
+
modalDensities = MODAL_DENSITIES;
|
|
1251
|
+
backStrategies = BACK_STRATEGIES;
|
|
1252
|
+
paramTargets = PARAM_TARGETS;
|
|
1253
|
+
initialValueDrafts = signal({}, ...(ngDevMode ? [{ debugName: "initialValueDrafts" }] : []));
|
|
1254
|
+
injectedData = inject(SETTINGS_PANEL_DATA, { optional: true });
|
|
1255
|
+
i18n = inject(PraxisI18nService);
|
|
1256
|
+
currentDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "currentDocument" }] : []));
|
|
1257
|
+
initialDocument = signal(createCrudAuthoringDocument({}), ...(ngDevMode ? [{ debugName: "initialDocument" }] : []));
|
|
1258
|
+
lastExternalSignature = null;
|
|
1259
|
+
effectiveCrudId = computed(() => this.crudIdInput() || this.injectedData?.crudId || null, ...(ngDevMode ? [{ debugName: "effectiveCrudId" }] : []));
|
|
1260
|
+
isReadonly = computed(() => !!(this.readonlyInput() || this.injectedData?.readonly), ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
|
|
1261
|
+
diagnostics = computed(() => validateCrudAuthoringDocument(this.currentDocument(), { requireCanonicalActions: true }), ...(ngDevMode ? [{ debugName: "diagnostics" }] : []));
|
|
1262
|
+
errorCount = computed(() => this.diagnostics().filter((issue) => issue.level === 'error').length, ...(ngDevMode ? [{ debugName: "errorCount" }] : []));
|
|
1263
|
+
warningCount = computed(() => this.diagnostics().filter((issue) => issue.level !== 'error').length, ...(ngDevMode ? [{ debugName: "warningCount" }] : []));
|
|
1264
|
+
serializedDocument = computed(() => JSON.stringify(serializeCrudAuthoringDocument(this.currentDocument()), null, 2), ...(ngDevMode ? [{ debugName: "serializedDocument" }] : []));
|
|
1265
|
+
tableConfig = computed(() => this.currentDocument().metadata.table || { columns: [] }, ...(ngDevMode ? [{ debugName: "tableConfig" }] : []));
|
|
1266
|
+
resourcePath = computed(() => this.currentDocument().metadata.resource?.path || '', ...(ngDevMode ? [{ debugName: "resourcePath" }] : []));
|
|
1267
|
+
resourceIdField = computed(() => String(this.currentDocument().metadata.resource?.idField ?? ''), ...(ngDevMode ? [{ debugName: "resourceIdField" }] : []));
|
|
1268
|
+
resourceEndpointKey = computed(() => String(this.currentDocument().metadata.resource?.endpointKey ?? ''), ...(ngDevMode ? [{ debugName: "resourceEndpointKey" }] : []));
|
|
1269
|
+
defaultsOpenMode = computed(() => this.currentDocument().metadata.defaults?.openMode || 'route', ...(ngDevMode ? [{ debugName: "defaultsOpenMode" }] : []));
|
|
1270
|
+
headerShowBack = computed(() => !!this.currentDocument().metadata.defaults?.header?.showBack, ...(ngDevMode ? [{ debugName: "headerShowBack" }] : []));
|
|
1271
|
+
headerBackLabel = computed(() => this.currentDocument().metadata.defaults?.header?.backLabel || '', ...(ngDevMode ? [{ debugName: "headerBackLabel" }] : []));
|
|
1272
|
+
headerVariant = computed(() => this.currentDocument().metadata.defaults?.header?.variant || 'ghost', ...(ngDevMode ? [{ debugName: "headerVariant" }] : []));
|
|
1273
|
+
headerSticky = computed(() => !!this.currentDocument().metadata.defaults?.header?.sticky, ...(ngDevMode ? [{ debugName: "headerSticky" }] : []));
|
|
1274
|
+
headerBreadcrumbs = computed(() => !!this.currentDocument().metadata.defaults?.header?.breadcrumbs, ...(ngDevMode ? [{ debugName: "headerBreadcrumbs" }] : []));
|
|
1275
|
+
headerDivider = computed(() => !!this.currentDocument().metadata.defaults?.header?.divider, ...(ngDevMode ? [{ debugName: "headerDivider" }] : []));
|
|
1276
|
+
modalDensity = computed(() => String(this.currentDocument().metadata.defaults?.modal?.density ?? 'default'), ...(ngDevMode ? [{ debugName: "modalDensity" }] : []));
|
|
1277
|
+
modalCanMaximize = computed(() => this.currentDocument().metadata.defaults?.modal?.canMaximize ?? true, ...(ngDevMode ? [{ debugName: "modalCanMaximize" }] : []));
|
|
1278
|
+
modalStartMaximized = computed(() => !!this.currentDocument().metadata.defaults?.modal?.startMaximized, ...(ngDevMode ? [{ debugName: "modalStartMaximized" }] : []));
|
|
1279
|
+
modalRememberLastState = computed(() => !!this.currentDocument().metadata.defaults?.modal?.rememberLastState, ...(ngDevMode ? [{ debugName: "modalRememberLastState" }] : []));
|
|
1280
|
+
modalDisableCloseOnEsc = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnEsc, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnEsc" }] : []));
|
|
1281
|
+
modalDisableCloseOnBackdrop = computed(() => !!this.currentDocument().metadata.defaults?.modal?.disableCloseOnBackdrop, ...(ngDevMode ? [{ debugName: "modalDisableCloseOnBackdrop" }] : []));
|
|
1282
|
+
modalFullscreenBreakpoint = computed(() => String(this.currentDocument().metadata.defaults?.modal?.fullscreenBreakpoint ?? ''), ...(ngDevMode ? [{ debugName: "modalFullscreenBreakpoint" }] : []));
|
|
1283
|
+
backStrategy = computed(() => String(this.currentDocument().metadata.defaults?.back?.strategy ?? 'auto'), ...(ngDevMode ? [{ debugName: "backStrategy" }] : []));
|
|
1284
|
+
backReturnTo = computed(() => this.currentDocument().metadata.defaults?.back?.returnTo || '', ...(ngDevMode ? [{ debugName: "backReturnTo" }] : []));
|
|
1285
|
+
backConfirmOnDirty = computed(() => !!this.currentDocument().metadata.defaults?.back?.confirmOnDirty, ...(ngDevMode ? [{ debugName: "backConfirmOnDirty" }] : []));
|
|
1286
|
+
nextFocusSection = computed(() => {
|
|
1287
|
+
for (const section of ['connection', 'defaults', 'actions', 'table']) {
|
|
1288
|
+
if (this.sectionStatus(section) === 'invalid') {
|
|
1289
|
+
return section;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
for (const section of ['connection', 'defaults', 'actions', 'table']) {
|
|
1293
|
+
if (this.sectionStatus(section) === 'pending') {
|
|
1294
|
+
return section;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return 'connection';
|
|
1298
|
+
}, ...(ngDevMode ? [{ debugName: "nextFocusSection" }] : []));
|
|
1299
|
+
nextFocusStatus = computed(() => this.sectionStatus(this.nextFocusSection()), ...(ngDevMode ? [{ debugName: "nextFocusStatus" }] : []));
|
|
1300
|
+
visibleHealthBuckets = computed(() => {
|
|
1301
|
+
const attentionBuckets = ['invalid', 'pending']
|
|
1302
|
+
.filter((bucket) => this.hasHealthBucketContent(bucket));
|
|
1303
|
+
if (attentionBuckets.length) {
|
|
1304
|
+
return attentionBuckets;
|
|
1305
|
+
}
|
|
1306
|
+
return this.hasHealthBucketContent('ready') ? ['ready'] : [];
|
|
1307
|
+
}, ...(ngDevMode ? [{ debugName: "visibleHealthBuckets" }] : []));
|
|
1308
|
+
showHealthMap = computed(() => this.visibleHealthBuckets().length > 1, ...(ngDevMode ? [{ debugName: "showHealthMap" }] : []));
|
|
1309
|
+
groupedDiagnostics = computed(() => ['connection', 'defaults', 'actions', 'table']
|
|
1310
|
+
.map((section) => {
|
|
1311
|
+
const issues = this.sectionDiagnostics(section);
|
|
1312
|
+
return {
|
|
1313
|
+
section,
|
|
1314
|
+
issues,
|
|
1315
|
+
hasError: issues.some((issue) => issue.level === 'error'),
|
|
1316
|
+
};
|
|
1317
|
+
})
|
|
1318
|
+
.filter((group) => group.issues.length > 0), ...(ngDevMode ? [{ debugName: "groupedDiagnostics" }] : []));
|
|
1319
|
+
constructor() {
|
|
1320
|
+
effect(() => {
|
|
1321
|
+
const seed = this.resolveExternalSeed();
|
|
1322
|
+
const signature = JSON.stringify(seed);
|
|
1323
|
+
if (signature === this.lastExternalSignature) {
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
this.lastExternalSignature = signature;
|
|
1327
|
+
const parsed = parseLegacyOrCrudDocument(seed);
|
|
1328
|
+
this.initialDocument.set(parsed);
|
|
1329
|
+
this.currentDocument.set(parsed);
|
|
1330
|
+
this.syncState();
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
getSettingsValue() {
|
|
1334
|
+
return this.buildPayload();
|
|
1335
|
+
}
|
|
1336
|
+
onSave() {
|
|
1337
|
+
return this.buildPayload();
|
|
1338
|
+
}
|
|
1339
|
+
reset() {
|
|
1340
|
+
this.currentDocument.set(this.initialDocument());
|
|
1341
|
+
this.initialValueDrafts.set({});
|
|
1342
|
+
this.syncState();
|
|
1343
|
+
}
|
|
1344
|
+
actionValue(actionName) {
|
|
1345
|
+
return (findCrudAction(this.currentDocument().metadata.actions, actionName) || {
|
|
1346
|
+
id: actionName,
|
|
1347
|
+
action: actionName,
|
|
1348
|
+
label: this.actionLabel(actionName),
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
actionApiUrlEntryDraft(actionName) {
|
|
1352
|
+
const value = this.actionValue(actionName).form?.apiUrlEntry;
|
|
1353
|
+
return value ? JSON.stringify(value, null, 2) : '';
|
|
1354
|
+
}
|
|
1355
|
+
actionParams(actionName) {
|
|
1356
|
+
return [...(this.actionValue(actionName).params || [])];
|
|
1357
|
+
}
|
|
1358
|
+
actionInitialValueDraft(actionName) {
|
|
1359
|
+
const draft = this.initialValueDrafts()[actionName];
|
|
1360
|
+
if (draft !== undefined) {
|
|
1361
|
+
return draft;
|
|
1362
|
+
}
|
|
1363
|
+
const value = this.actionValue(actionName).form?.initialValue;
|
|
1364
|
+
return value ? JSON.stringify(value, null, 2) : '';
|
|
1365
|
+
}
|
|
1366
|
+
actionLabel(actionName) {
|
|
1367
|
+
return this.tx(`crud.authoring.action.${actionName}`, actionName);
|
|
1368
|
+
}
|
|
1369
|
+
actionSummary(actionName) {
|
|
1370
|
+
const action = this.actionValue(actionName);
|
|
1371
|
+
const mode = (action.openMode || this.defaultsOpenMode());
|
|
1372
|
+
const binding = action.formId || action.route || this.modeLabel(mode);
|
|
1373
|
+
if (this.actionStatus(actionName) === 'ready') {
|
|
1374
|
+
return `${binding} | ${this.tx('crud.authoring.action.headerReady', 'Ready')}`;
|
|
1375
|
+
}
|
|
1376
|
+
return `${binding} | ${this.actionEditorialSummary(actionName)}`;
|
|
1377
|
+
}
|
|
1378
|
+
actionEffectiveBindingSummary(actionName) {
|
|
1379
|
+
const action = this.actionValue(actionName);
|
|
1380
|
+
if (action.formId) {
|
|
1381
|
+
return `${this.tx('crud.authoring.action.binding', 'Binding')}: ${action.formId}`;
|
|
1382
|
+
}
|
|
1383
|
+
if (action.route) {
|
|
1384
|
+
return `${this.tx('crud.authoring.action.binding', 'Binding')}: ${action.route}`;
|
|
1385
|
+
}
|
|
1386
|
+
return this.tx('crud.authoring.action.bindingMissing', 'Binding not configured yet');
|
|
1387
|
+
}
|
|
1388
|
+
actionSubmitSummary(actionName) {
|
|
1389
|
+
const action = this.actionValue(actionName);
|
|
1390
|
+
const method = action.form?.submitMethod?.toUpperCase();
|
|
1391
|
+
const submitUrl = action.form?.submitUrl;
|
|
1392
|
+
if (method && submitUrl) {
|
|
1393
|
+
return `${method} ${submitUrl}`;
|
|
1394
|
+
}
|
|
1395
|
+
if (action.form?.schemaUrl) {
|
|
1396
|
+
return `${this.tx('crud.authoring.action.schema', 'Schema')}: ${action.form.schemaUrl}`;
|
|
1397
|
+
}
|
|
1398
|
+
return this.tx('crud.authoring.action.submitPending', 'Submit/API details not configured yet');
|
|
1399
|
+
}
|
|
1400
|
+
actionSchemaSummary(actionName) {
|
|
1401
|
+
const schemaUrl = this.actionValue(actionName).form?.schemaUrl;
|
|
1402
|
+
if (schemaUrl) {
|
|
1403
|
+
return `${this.tx('crud.authoring.action.schema', 'Schema')}: ${schemaUrl}`;
|
|
1404
|
+
}
|
|
1405
|
+
return this.tx('crud.authoring.action.schemaPending', 'Schema contract not configured yet');
|
|
1406
|
+
}
|
|
1407
|
+
actionApiSummary(actionName) {
|
|
1408
|
+
const form = this.actionValue(actionName).form;
|
|
1409
|
+
if (form?.apiEndpointKey) {
|
|
1410
|
+
return `${this.tx('crud.authoring.action.api', 'API')}: ${form.apiEndpointKey}`;
|
|
1411
|
+
}
|
|
1412
|
+
if (form?.apiUrlEntry) {
|
|
1413
|
+
return this.tx('crud.authoring.action.apiEntryReady', 'Custom API URL entry configured');
|
|
1414
|
+
}
|
|
1415
|
+
return this.tx('crud.authoring.action.apiPending', 'API mapping not configured yet');
|
|
1416
|
+
}
|
|
1417
|
+
actionInputsNote(actionName) {
|
|
1418
|
+
void actionName;
|
|
1419
|
+
return this.tx('crud.authoring.action.inputsNote', 'Optional: use Params for row-derived values and Initial value only for fixed form seed.');
|
|
1420
|
+
}
|
|
1421
|
+
actionInputsSummary(actionName) {
|
|
1422
|
+
const parts = [];
|
|
1423
|
+
const params = this.actionParams(actionName);
|
|
1424
|
+
if (params.length) {
|
|
1425
|
+
parts.push(this.tx('crud.authoring.action.paramsSummary', '{count} mappings')
|
|
1426
|
+
.replace('{count}', String(params.length)));
|
|
1427
|
+
}
|
|
1428
|
+
else {
|
|
1429
|
+
parts.push(this.tx('crud.authoring.action.paramsNone', 'No param mappings yet'));
|
|
1430
|
+
}
|
|
1431
|
+
const draft = this.actionInitialValueDraft(actionName).trim();
|
|
1432
|
+
const hasInitialValue = !!this.actionValue(actionName).form?.initialValue;
|
|
1433
|
+
if (draft && !hasInitialValue) {
|
|
1434
|
+
parts.push(this.tx('crud.authoring.action.initialValueInvalid', 'Initial value JSON is not valid yet'));
|
|
1435
|
+
}
|
|
1436
|
+
else if (hasInitialValue) {
|
|
1437
|
+
parts.push(this.tx('crud.authoring.action.initialValueReady', 'Initial value seed configured'));
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
parts.push(this.tx('crud.authoring.action.initialValueEmpty', 'No fixed form seed yet'));
|
|
1441
|
+
}
|
|
1442
|
+
return parts.join(' | ');
|
|
1443
|
+
}
|
|
1444
|
+
actionParamsNote(actionName) {
|
|
1445
|
+
const params = this.actionParams(actionName);
|
|
1446
|
+
if (!params.length) {
|
|
1447
|
+
return this.tx('crud.authoring.action.paramsNoteEmpty', 'Map fields from the current row into route params, query string, or dialog inputs.');
|
|
1448
|
+
}
|
|
1449
|
+
return this.tx('crud.authoring.action.paramsNoteReady', 'Mappings ready');
|
|
1450
|
+
}
|
|
1451
|
+
actionInitialValueNote(actionName) {
|
|
1452
|
+
const draft = this.actionInitialValueDraft(actionName).trim();
|
|
1453
|
+
const hasInitialValue = !!this.actionValue(actionName).form?.initialValue;
|
|
1454
|
+
if (draft && !hasInitialValue) {
|
|
1455
|
+
return this.tx('crud.authoring.action.initialValueInvalid', 'Initial value JSON is not valid yet');
|
|
1456
|
+
}
|
|
1457
|
+
if (hasInitialValue) {
|
|
1458
|
+
return this.tx('crud.authoring.action.initialValueNoteReady', 'Fixed seed ready');
|
|
1459
|
+
}
|
|
1460
|
+
return this.tx('crud.authoring.action.initialValueNoteEmpty', 'Use this only for fixed seed values that do not come from the selected row.');
|
|
1461
|
+
}
|
|
1462
|
+
actionAdvancedSummary(actionName) {
|
|
1463
|
+
const submitStatus = this.actionGroupStatus(actionName, 'submit');
|
|
1464
|
+
const apiStatus = this.actionGroupStatus(actionName, 'api');
|
|
1465
|
+
const inputsSummary = this.actionInputsSummaryBadge(actionName);
|
|
1466
|
+
if (submitStatus === 'ready' && apiStatus === 'ready') {
|
|
1467
|
+
return this.tx('crud.authoring.action.advancedReady', 'Advanced ready');
|
|
1468
|
+
}
|
|
1469
|
+
const pending = [];
|
|
1470
|
+
if (submitStatus !== 'ready') {
|
|
1471
|
+
pending.push(this.tx('crud.authoring.action.submitGroup', 'Submit contract'));
|
|
1472
|
+
}
|
|
1473
|
+
if (apiStatus !== 'ready') {
|
|
1474
|
+
pending.push(this.tx('crud.authoring.action.apiGroup', 'API mapping'));
|
|
1475
|
+
}
|
|
1476
|
+
const summary = this.tx('crud.authoring.action.advancedPending', 'Pending: {groups}')
|
|
1477
|
+
.replace('{groups}', pending.join(', '));
|
|
1478
|
+
return inputsSummary ? `${summary} · ${inputsSummary}` : summary;
|
|
1479
|
+
}
|
|
1480
|
+
actionPrimarySummary(actionName) {
|
|
1481
|
+
const bindingStatus = this.actionGroupStatus(actionName, 'binding');
|
|
1482
|
+
const schemaStatus = this.actionGroupStatus(actionName, 'schema');
|
|
1483
|
+
if (bindingStatus === 'ready' && schemaStatus === 'ready') {
|
|
1484
|
+
return this.tx('crud.authoring.action.primaryReady', 'Core ready');
|
|
1485
|
+
}
|
|
1486
|
+
const invalid = [];
|
|
1487
|
+
if (bindingStatus === 'invalid') {
|
|
1488
|
+
invalid.push(this.tx('crud.authoring.action.bindingGroup', 'Binding'));
|
|
1489
|
+
}
|
|
1490
|
+
if (schemaStatus === 'invalid') {
|
|
1491
|
+
invalid.push(this.tx('crud.authoring.action.schemaGroup', 'Schema contract'));
|
|
1492
|
+
}
|
|
1493
|
+
if (invalid.length) {
|
|
1494
|
+
return this.tx('crud.authoring.action.primaryInvalid', 'Resolve: {groups}')
|
|
1495
|
+
.replace('{groups}', invalid.join(', '));
|
|
1496
|
+
}
|
|
1497
|
+
const pending = [];
|
|
1498
|
+
if (bindingStatus !== 'ready') {
|
|
1499
|
+
pending.push(this.tx('crud.authoring.action.bindingGroup', 'Binding'));
|
|
1500
|
+
}
|
|
1501
|
+
if (schemaStatus !== 'ready') {
|
|
1502
|
+
pending.push(this.tx('crud.authoring.action.schemaGroup', 'Schema contract'));
|
|
1503
|
+
}
|
|
1504
|
+
return this.tx('crud.authoring.action.primaryPending', 'Next: {groups}')
|
|
1505
|
+
.replace('{groups}', pending.join(', '));
|
|
1506
|
+
}
|
|
1507
|
+
actionBindingNote(actionName) {
|
|
1508
|
+
if (this.actionGroupStatus(actionName, 'binding') === 'ready') {
|
|
1509
|
+
return this.tx('crud.authoring.action.bindingReadyNote', 'Binding configured');
|
|
1510
|
+
}
|
|
1511
|
+
return this.actionEffectiveBindingSummary(actionName);
|
|
1512
|
+
}
|
|
1513
|
+
actionSchemaNote(actionName) {
|
|
1514
|
+
if (this.actionGroupStatus(actionName, 'schema') === 'ready') {
|
|
1515
|
+
return this.tx('crud.authoring.action.schemaReadyNote', 'Schema linked');
|
|
1516
|
+
}
|
|
1517
|
+
return this.actionSchemaSummary(actionName);
|
|
1518
|
+
}
|
|
1519
|
+
actionSubmitNote(actionName) {
|
|
1520
|
+
if (this.actionGroupStatus(actionName, 'submit') === 'ready') {
|
|
1521
|
+
return this.tx('crud.authoring.action.submitReadyNote', 'Submit contract ready');
|
|
1522
|
+
}
|
|
1523
|
+
return this.actionSubmitSummary(actionName);
|
|
1524
|
+
}
|
|
1525
|
+
actionApiNote(actionName) {
|
|
1526
|
+
if (this.actionGroupStatus(actionName, 'api') === 'ready') {
|
|
1527
|
+
return this.tx('crud.authoring.action.apiReadyNote', 'API mapping ready');
|
|
1528
|
+
}
|
|
1529
|
+
return this.actionApiSummary(actionName);
|
|
1530
|
+
}
|
|
1531
|
+
actionBackStrategy(actionName) {
|
|
1532
|
+
return String(this.actionValue(actionName).back?.strategy ?? '');
|
|
1533
|
+
}
|
|
1534
|
+
actionBackReturnTo(actionName) {
|
|
1535
|
+
return this.actionValue(actionName).back?.returnTo || '';
|
|
1536
|
+
}
|
|
1537
|
+
actionBackConfirmOnDirty(actionName) {
|
|
1538
|
+
return !!this.actionValue(actionName).back?.confirmOnDirty;
|
|
1539
|
+
}
|
|
1540
|
+
actionBackNote(actionName) {
|
|
1541
|
+
const actionBack = this.actionValue(actionName).back;
|
|
1542
|
+
if (!actionBack || Object.keys(actionBack).length === 0) {
|
|
1543
|
+
return this.tx('crud.authoring.action.backUsesDefaults', 'Using CRUD defaults for back behavior');
|
|
1544
|
+
}
|
|
1545
|
+
const parts = [];
|
|
1546
|
+
if (actionBack.strategy) {
|
|
1547
|
+
parts.push(this.tx('crud.authoring.action.backNoteStrategy', 'Strategy: {value}')
|
|
1548
|
+
.replace('{value}', this.backStrategyLabel(actionBack.strategy)));
|
|
1549
|
+
}
|
|
1550
|
+
if (actionBack.returnTo) {
|
|
1551
|
+
parts.push(this.tx('crud.authoring.action.backNoteReturnTo', 'Return: {value}')
|
|
1552
|
+
.replace('{value}', actionBack.returnTo));
|
|
1553
|
+
}
|
|
1554
|
+
if (actionBack.confirmOnDirty) {
|
|
1555
|
+
parts.push(this.tx('crud.authoring.action.backNoteConfirmOnDirty', 'Dirty confirmation'));
|
|
1556
|
+
}
|
|
1557
|
+
return parts.join(' | ');
|
|
1558
|
+
}
|
|
1559
|
+
actionGroupStatus(actionName, group) {
|
|
1560
|
+
const action = this.actionValue(actionName);
|
|
1561
|
+
const effectiveMode = (action.openMode || this.defaultsOpenMode());
|
|
1562
|
+
const actionDiagnostics = this.actionDiagnostics(actionName);
|
|
1563
|
+
if (group === 'binding') {
|
|
1564
|
+
const hasBindingError = actionDiagnostics.some((issue) => (issue.code === 'crud.metadata.actions.route.required'
|
|
1565
|
+
|| issue.code === 'crud.metadata.actions.formId.required'));
|
|
1566
|
+
if (hasBindingError) {
|
|
1567
|
+
return 'invalid';
|
|
1568
|
+
}
|
|
1569
|
+
const hasBinding = effectiveMode === 'route'
|
|
1570
|
+
? !!String(action.route || '').trim()
|
|
1571
|
+
: !!String(action.formId || '').trim();
|
|
1572
|
+
return hasBinding ? 'ready' : 'pending';
|
|
1573
|
+
}
|
|
1574
|
+
if (group === 'schema') {
|
|
1575
|
+
return String(action.form?.schemaUrl || '').trim() ? 'ready' : 'pending';
|
|
1576
|
+
}
|
|
1577
|
+
if (group === 'submit') {
|
|
1578
|
+
const hasSubmitError = actionDiagnostics.some((issue) => issue.code === 'crud.metadata.actions.form.submit-contract.invalid');
|
|
1579
|
+
if (hasSubmitError) {
|
|
1580
|
+
return 'invalid';
|
|
1581
|
+
}
|
|
1582
|
+
const hasSubmitPair = !!String(action.form?.submitUrl || '').trim()
|
|
1583
|
+
&& !!String(action.form?.submitMethod || '').trim();
|
|
1584
|
+
return hasSubmitPair ? 'ready' : 'pending';
|
|
1585
|
+
}
|
|
1586
|
+
const hasApiEndpoint = !!String(action.form?.apiEndpointKey || '').trim();
|
|
1587
|
+
const hasApiEntry = !!String(this.actionApiUrlEntryDraft(actionName) || '').trim();
|
|
1588
|
+
return hasApiEndpoint || hasApiEntry ? 'ready' : 'pending';
|
|
1589
|
+
}
|
|
1590
|
+
actionGroupStatusLabel(actionName, group) {
|
|
1591
|
+
return {
|
|
1592
|
+
ready: this.tx('crud.authoring.action.status.ready', 'Ready'),
|
|
1593
|
+
pending: this.tx('crud.authoring.action.status.pending', 'Pending'),
|
|
1594
|
+
invalid: this.tx('crud.authoring.action.status.invalid', 'Invalid'),
|
|
1595
|
+
}[this.actionGroupStatus(actionName, group)];
|
|
1596
|
+
}
|
|
1597
|
+
actionGroupLabel(group) {
|
|
1598
|
+
return {
|
|
1599
|
+
binding: this.tx('crud.authoring.action.bindingGroup', 'Binding'),
|
|
1600
|
+
schema: this.tx('crud.authoring.action.schemaGroup', 'Schema contract'),
|
|
1601
|
+
submit: this.tx('crud.authoring.action.submitGroup', 'Submit contract'),
|
|
1602
|
+
api: this.tx('crud.authoring.action.apiGroup', 'API mapping'),
|
|
1603
|
+
}[group];
|
|
1604
|
+
}
|
|
1605
|
+
actionGroupsSummary(actionName) {
|
|
1606
|
+
const invalidGroups = this.actionGroupLabelsByStatus(actionName, 'invalid');
|
|
1607
|
+
if (invalidGroups.length) {
|
|
1608
|
+
return this.tx('crud.authoring.action.groupsInvalid', 'Invalid: {groups}')
|
|
1609
|
+
.replace('{groups}', invalidGroups.join(', '));
|
|
1610
|
+
}
|
|
1611
|
+
const pendingGroups = this.actionGroupLabelsByStatus(actionName, 'pending');
|
|
1612
|
+
if (pendingGroups.length) {
|
|
1613
|
+
return this.tx('crud.authoring.action.groupsPending', 'Pending: {groups}')
|
|
1614
|
+
.replace('{groups}', pendingGroups.join(', '));
|
|
1615
|
+
}
|
|
1616
|
+
return this.tx('crud.authoring.action.groupsReady', 'All groups ready');
|
|
1617
|
+
}
|
|
1618
|
+
actionEditorialSummary(actionName) {
|
|
1619
|
+
const invalidGroups = this.actionGroupLabelsByStatus(actionName, 'invalid');
|
|
1620
|
+
if (invalidGroups.length) {
|
|
1621
|
+
return this.tx('crud.authoring.action.editorialInvalid', 'Resolve: {groups}')
|
|
1622
|
+
.replace('{groups}', invalidGroups.join(', '));
|
|
1623
|
+
}
|
|
1624
|
+
const pendingGroups = this.actionGroupLabelsByStatus(actionName, 'pending');
|
|
1625
|
+
if (pendingGroups.length) {
|
|
1626
|
+
return this.tx('crud.authoring.action.editorialPending', 'Finish: {groups}')
|
|
1627
|
+
.replace('{groups}', pendingGroups.join(', '));
|
|
1628
|
+
}
|
|
1629
|
+
return this.tx('crud.authoring.action.editorialReady', 'Ready to use');
|
|
1630
|
+
}
|
|
1631
|
+
actionPanelExpanded(actionName) {
|
|
1632
|
+
return this.actionStatus(actionName) !== 'ready';
|
|
1633
|
+
}
|
|
1634
|
+
actionAdvancedPanelExpanded(actionName) {
|
|
1635
|
+
return this.actionGroupStatus(actionName, 'submit') !== 'ready'
|
|
1636
|
+
|| this.actionGroupStatus(actionName, 'api') !== 'ready';
|
|
1637
|
+
}
|
|
1638
|
+
actionStatus(actionName) {
|
|
1639
|
+
const diagnosticPaths = this.actionDiagnostics(actionName);
|
|
1640
|
+
if (diagnosticPaths.some((issue) => issue.level === 'error')) {
|
|
1641
|
+
return 'invalid';
|
|
1642
|
+
}
|
|
1643
|
+
const action = this.actionValue(actionName);
|
|
1644
|
+
const effectiveMode = (action.openMode || this.defaultsOpenMode());
|
|
1645
|
+
const hasBinding = effectiveMode === 'route'
|
|
1646
|
+
? !!String(action.route || '').trim()
|
|
1647
|
+
: !!String(action.formId || '').trim();
|
|
1648
|
+
const hasSubmitPair = !!String(action.form?.submitUrl || '').trim()
|
|
1649
|
+
&& !!String(action.form?.submitMethod || '').trim();
|
|
1650
|
+
const hasSchema = !!String(action.form?.schemaUrl || '').trim();
|
|
1651
|
+
if (hasBinding && (hasSubmitPair || hasSchema)) {
|
|
1652
|
+
return 'ready';
|
|
1653
|
+
}
|
|
1654
|
+
return 'pending';
|
|
1655
|
+
}
|
|
1656
|
+
actionStatusLabel(actionName) {
|
|
1657
|
+
return {
|
|
1658
|
+
ready: this.tx('crud.authoring.action.status.ready', 'Ready'),
|
|
1659
|
+
pending: this.tx('crud.authoring.action.status.pending', 'Pending'),
|
|
1660
|
+
invalid: this.tx('crud.authoring.action.status.invalid', 'Invalid'),
|
|
1661
|
+
}[this.actionStatus(actionName)];
|
|
1662
|
+
}
|
|
1663
|
+
actionShowStatusChip(actionName) {
|
|
1664
|
+
return this.actionStatus(actionName) !== 'ready';
|
|
1665
|
+
}
|
|
1666
|
+
connectionOverview() {
|
|
1667
|
+
const path = this.resourcePath();
|
|
1668
|
+
if (!path) {
|
|
1669
|
+
return this.tx('crud.authoring.overview.connectionMissing', 'Resource not configured yet');
|
|
1670
|
+
}
|
|
1671
|
+
return path;
|
|
1672
|
+
}
|
|
1673
|
+
connectionOverviewNote() {
|
|
1674
|
+
const idField = this.resourceIdField();
|
|
1675
|
+
const endpointKey = this.resourceEndpointKey();
|
|
1676
|
+
const parts = [];
|
|
1677
|
+
if (idField) {
|
|
1678
|
+
parts.push(this.tx('crud.authoring.overview.connectionIdField', 'ID: {value}').replace('{value}', idField));
|
|
1679
|
+
}
|
|
1680
|
+
if (endpointKey) {
|
|
1681
|
+
parts.push(this.tx('crud.authoring.overview.connectionEndpoint', 'API: {value}').replace('{value}', endpointKey));
|
|
1682
|
+
}
|
|
1683
|
+
if (!parts.length) {
|
|
1684
|
+
return this.tx('crud.authoring.overview.connectionDetailsPending', 'Identifier and endpoint details are still optional here.');
|
|
1685
|
+
}
|
|
1686
|
+
return parts.join(' · ');
|
|
1687
|
+
}
|
|
1688
|
+
defaultsOverview() {
|
|
1689
|
+
return this.modeLabel(this.defaultsOpenMode());
|
|
1690
|
+
}
|
|
1691
|
+
defaultsOverviewNote() {
|
|
1692
|
+
const parts = [
|
|
1693
|
+
this.headerShowBack()
|
|
1694
|
+
? this.tx('crud.authoring.overview.backVisible', 'Back visible')
|
|
1695
|
+
: this.tx('crud.authoring.overview.backHidden', 'Back hidden'),
|
|
1696
|
+
];
|
|
1697
|
+
if (this.modalDensity() !== 'default') {
|
|
1698
|
+
parts.push(this.tx('crud.authoring.overview.modalDensity', 'Modal: {value}')
|
|
1699
|
+
.replace('{value}', this.modalDensityLabel(this.modalDensity())));
|
|
1700
|
+
}
|
|
1701
|
+
if (this.backStrategy() !== 'auto') {
|
|
1702
|
+
parts.push(this.tx('crud.authoring.overview.backStrategy', 'Back: {value}')
|
|
1703
|
+
.replace('{value}', this.backStrategyLabel(this.backStrategy())));
|
|
1704
|
+
}
|
|
1705
|
+
if (this.modalRememberLastState()) {
|
|
1706
|
+
parts.push(this.tx('crud.authoring.overview.modalRememberState', 'State remembered'));
|
|
1707
|
+
}
|
|
1708
|
+
if (this.backConfirmOnDirty()) {
|
|
1709
|
+
parts.push(this.tx('crud.authoring.overview.backConfirmOnDirty', 'Dirty confirm'));
|
|
1710
|
+
}
|
|
1711
|
+
if (this.headerSticky()) {
|
|
1712
|
+
parts.push(this.tx('crud.authoring.overview.headerSticky', 'Sticky header'));
|
|
1713
|
+
}
|
|
1714
|
+
if (this.headerBreadcrumbs()) {
|
|
1715
|
+
parts.push(this.tx('crud.authoring.overview.headerBreadcrumbs', 'Breadcrumbs'));
|
|
1716
|
+
}
|
|
1717
|
+
return parts.join(' · ');
|
|
1718
|
+
}
|
|
1719
|
+
modalDefaultsNote() {
|
|
1720
|
+
const parts = [
|
|
1721
|
+
this.tx('crud.authoring.defaults.modal.noteDensity', 'Density: {value}')
|
|
1722
|
+
.replace('{value}', this.modalDensityLabel(this.modalDensity())),
|
|
1723
|
+
];
|
|
1724
|
+
if (!this.modalCanMaximize()) {
|
|
1725
|
+
parts.push(this.tx('crud.authoring.defaults.modal.noteNoMaximize', 'Maximize hidden'));
|
|
1726
|
+
}
|
|
1727
|
+
if (this.modalStartMaximized()) {
|
|
1728
|
+
parts.push(this.tx('crud.authoring.defaults.modal.noteStartMaximized', 'Starts maximized'));
|
|
1729
|
+
}
|
|
1730
|
+
if (this.modalRememberLastState()) {
|
|
1731
|
+
parts.push(this.tx('crud.authoring.defaults.modal.noteRememberState', 'Last size remembered'));
|
|
1732
|
+
}
|
|
1733
|
+
if (this.modalDisableCloseOnEsc()) {
|
|
1734
|
+
parts.push(this.tx('crud.authoring.defaults.modal.noteEscLocked', 'Escape locked'));
|
|
1735
|
+
}
|
|
1736
|
+
if (this.modalDisableCloseOnBackdrop()) {
|
|
1737
|
+
parts.push(this.tx('crud.authoring.defaults.modal.noteBackdropLocked', 'Backdrop locked'));
|
|
1738
|
+
}
|
|
1739
|
+
if (this.modalFullscreenBreakpoint().trim()) {
|
|
1740
|
+
parts.push(this.tx('crud.authoring.defaults.modal.noteBreakpoint', 'Fullscreen at {value}px')
|
|
1741
|
+
.replace('{value}', this.modalFullscreenBreakpoint().trim()));
|
|
1742
|
+
}
|
|
1743
|
+
return parts.join(' · ');
|
|
1744
|
+
}
|
|
1745
|
+
backDefaultsNote() {
|
|
1746
|
+
const parts = [
|
|
1747
|
+
this.tx('crud.authoring.defaults.back.noteStrategy', 'Strategy: {value}')
|
|
1748
|
+
.replace('{value}', this.backStrategyLabel(this.backStrategy())),
|
|
1749
|
+
];
|
|
1750
|
+
if (this.backReturnTo().trim()) {
|
|
1751
|
+
parts.push(this.tx('crud.authoring.defaults.back.noteReturnTo', 'Return: {value}')
|
|
1752
|
+
.replace('{value}', this.backReturnTo().trim()));
|
|
1753
|
+
}
|
|
1754
|
+
if (this.backConfirmOnDirty()) {
|
|
1755
|
+
parts.push(this.tx('crud.authoring.defaults.back.noteConfirmOnDirty', 'Dirty confirmation'));
|
|
1756
|
+
}
|
|
1757
|
+
return parts.join(' · ');
|
|
1758
|
+
}
|
|
1759
|
+
actionsOverview() {
|
|
1760
|
+
const readyCount = this.actionKeys.filter((actionName) => this.actionStatus(actionName) === 'ready').length;
|
|
1761
|
+
const pendingCount = this.actionKeys.filter((actionName) => this.actionStatus(actionName) === 'pending').length;
|
|
1762
|
+
const invalidCount = this.actionKeys.filter((actionName) => this.actionStatus(actionName) === 'invalid').length;
|
|
1763
|
+
const parts = [
|
|
1764
|
+
this.tx('crud.authoring.overview.actionsReady', '{count} ready').replace('{count}', String(readyCount)),
|
|
1765
|
+
];
|
|
1766
|
+
if (pendingCount > 0) {
|
|
1767
|
+
parts.push(this.tx('crud.authoring.overview.actionsPending', '{count} pending').replace('{count}', String(pendingCount)));
|
|
1768
|
+
}
|
|
1769
|
+
if (invalidCount > 0) {
|
|
1770
|
+
parts.push(this.tx('crud.authoring.overview.actionsInvalid', '{count} invalid').replace('{count}', String(invalidCount)));
|
|
1771
|
+
}
|
|
1772
|
+
return parts.join(' · ');
|
|
1773
|
+
}
|
|
1774
|
+
actionsOverviewNote() {
|
|
1775
|
+
const focusedAction = this.prioritizedActionKey();
|
|
1776
|
+
if (focusedAction && this.actionStatus(focusedAction) !== 'ready') {
|
|
1777
|
+
return this.tx('crud.authoring.overview.actionsNextCompact', 'Next: {action}. {summary}')
|
|
1778
|
+
.replace('{action}', this.actionLabel(focusedAction))
|
|
1779
|
+
.replace('{summary}', this.actionFocusSummary(focusedAction));
|
|
1780
|
+
}
|
|
1781
|
+
return this.tx('crud.authoring.overview.actionsTracked', '{count} tracked action blocks')
|
|
1782
|
+
.replace('{count}', String(this.actionKeys.length));
|
|
1783
|
+
}
|
|
1784
|
+
tableOverview() {
|
|
1785
|
+
const table = this.tableConfig();
|
|
1786
|
+
const title = table.toolbar?.title;
|
|
1787
|
+
const count = table.columns?.length || 0;
|
|
1788
|
+
if (title) {
|
|
1789
|
+
return title;
|
|
1790
|
+
}
|
|
1791
|
+
return `${count} ${this.tx('crud.authoring.overview.columns', 'columns')}`;
|
|
1792
|
+
}
|
|
1793
|
+
tableOverviewNote() {
|
|
1794
|
+
const table = this.tableConfig();
|
|
1795
|
+
const subtitle = table.toolbar?.subtitle;
|
|
1796
|
+
const parts = [this.tableBehaviorSummary(), this.tableStatesSummary()];
|
|
1797
|
+
if (subtitle) {
|
|
1798
|
+
parts.unshift(subtitle);
|
|
1799
|
+
}
|
|
1800
|
+
return parts.join(' · ');
|
|
1801
|
+
}
|
|
1802
|
+
tableStructureSummary() {
|
|
1803
|
+
const table = this.tableConfig();
|
|
1804
|
+
const count = table.columns?.length || 0;
|
|
1805
|
+
const density = table.appearance?.density
|
|
1806
|
+
? this.tx(`crud.authoring.table.density.${table.appearance.density}`, table.appearance.density)
|
|
1807
|
+
: this.tx('crud.authoring.table.density.comfortable', 'Comfortable');
|
|
1808
|
+
return this.tx('crud.authoring.table.structureSummary', '{count} columns | {density}')
|
|
1809
|
+
.replace('{count}', String(count))
|
|
1810
|
+
.replace('{density}', density);
|
|
1811
|
+
}
|
|
1812
|
+
tableBehaviorSummary() {
|
|
1813
|
+
const table = this.tableConfig();
|
|
1814
|
+
const pagination = table.behavior?.pagination;
|
|
1815
|
+
const sorting = table.behavior?.sorting;
|
|
1816
|
+
const parts = [];
|
|
1817
|
+
if (pagination?.enabled === false) {
|
|
1818
|
+
parts.push(this.tx('crud.authoring.table.paginationOff', 'Pagination off'));
|
|
1819
|
+
}
|
|
1820
|
+
else {
|
|
1821
|
+
parts.push(this.tx('crud.authoring.table.paginationSummary', 'Page size {value}')
|
|
1822
|
+
.replace('{value}', String(pagination?.pageSize || 10)));
|
|
1823
|
+
}
|
|
1824
|
+
if (sorting?.enabled === false) {
|
|
1825
|
+
parts.push(this.tx('crud.authoring.table.sortingOff', 'Sorting off'));
|
|
1826
|
+
}
|
|
1827
|
+
else {
|
|
1828
|
+
parts.push(sorting?.strategy === 'server'
|
|
1829
|
+
? this.tx('crud.authoring.table.sortingServer', 'Server sorting')
|
|
1830
|
+
: this.tx('crud.authoring.table.sortingClient', 'Client sorting'));
|
|
1831
|
+
}
|
|
1832
|
+
return parts.join(' | ');
|
|
1833
|
+
}
|
|
1834
|
+
tableStatesSummary() {
|
|
1835
|
+
const states = this.tableConfig().messages?.states;
|
|
1836
|
+
const customized = [
|
|
1837
|
+
states?.loading,
|
|
1838
|
+
states?.empty,
|
|
1839
|
+
states?.noResults,
|
|
1840
|
+
states?.error,
|
|
1841
|
+
].filter((value) => !!String(value || '').trim()).length;
|
|
1842
|
+
if (!customized) {
|
|
1843
|
+
return this.tx('crud.authoring.table.statesDefault', 'Default state copy');
|
|
1844
|
+
}
|
|
1845
|
+
return this.tx('crud.authoring.table.statesCustom', '{count} custom states')
|
|
1846
|
+
.replace('{count}', String(customized));
|
|
1847
|
+
}
|
|
1848
|
+
tableEditingFlowSummary() {
|
|
1849
|
+
return this.tx('crud.authoring.table.flowSummary', 'Core presentation stays visible below. Pagination, sorting, state copy, and columns stay in advanced table panels.');
|
|
1850
|
+
}
|
|
1851
|
+
validationOverview() {
|
|
1852
|
+
if (this.errorCount() > 0) {
|
|
1853
|
+
return this.tx('crud.authoring.overview.validationErrors', '{count} errors')
|
|
1854
|
+
.replace('{count}', String(this.errorCount()));
|
|
1855
|
+
}
|
|
1856
|
+
if (this.warningCount() > 0) {
|
|
1857
|
+
return this.tx('crud.authoring.overview.validationWarnings', '{count} warnings')
|
|
1858
|
+
.replace('{count}', String(this.warningCount()));
|
|
1859
|
+
}
|
|
1860
|
+
return this.tx('crud.authoring.overview.validationClean', 'No blocking diagnostics');
|
|
1861
|
+
}
|
|
1862
|
+
validationOverviewNote() {
|
|
1863
|
+
if (this.errorCount() > 0 || this.warningCount() > 0) {
|
|
1864
|
+
return this.tx('crud.authoring.overview.validationGrouped', 'Grouped by section below for faster troubleshooting.');
|
|
1865
|
+
}
|
|
1866
|
+
return this.tx('crud.authoring.overview.validationAligned', 'Shell guidance and diagnostics are aligned.');
|
|
1867
|
+
}
|
|
1868
|
+
jsonSectionSummary() {
|
|
1869
|
+
if (this.errorCount() > 0) {
|
|
1870
|
+
return this.tx('crud.authoring.json.summaryErrors', 'The canonical document still has validation errors.');
|
|
1871
|
+
}
|
|
1872
|
+
if (this.warningCount() > 0) {
|
|
1873
|
+
return this.tx('crud.authoring.json.summaryWarnings', 'The canonical document has diagnostics worth reviewing.');
|
|
1874
|
+
}
|
|
1875
|
+
return this.tx('crud.authoring.json.summaryClean', 'The canonical document is structurally consistent.');
|
|
1876
|
+
}
|
|
1877
|
+
sectionStatus(section) {
|
|
1878
|
+
if (this.sectionDiagnostics(section).some((issue) => issue.level === 'error')) {
|
|
1879
|
+
return 'invalid';
|
|
1880
|
+
}
|
|
1881
|
+
if (section === 'connection') {
|
|
1882
|
+
return this.resourcePath().trim() ? 'ready' : 'pending';
|
|
1883
|
+
}
|
|
1884
|
+
if (section === 'defaults') {
|
|
1885
|
+
return this.defaultsOpenMode() && this.headerVariant() ? 'ready' : 'pending';
|
|
1886
|
+
}
|
|
1887
|
+
if (section === 'actions') {
|
|
1888
|
+
return this.actionKeys.every((actionName) => this.actionStatus(actionName) === 'ready')
|
|
1889
|
+
? 'ready'
|
|
1890
|
+
: 'pending';
|
|
1891
|
+
}
|
|
1892
|
+
const table = this.tableConfig();
|
|
1893
|
+
const hasTitle = !!String(table.toolbar?.title || '').trim();
|
|
1894
|
+
const hasColumns = (table.columns?.length || 0) > 0;
|
|
1895
|
+
return hasTitle || hasColumns ? 'ready' : 'pending';
|
|
1896
|
+
}
|
|
1897
|
+
sectionStatusLabel(section) {
|
|
1898
|
+
return {
|
|
1899
|
+
ready: this.tx('crud.authoring.section.status.ready', 'Ready'),
|
|
1900
|
+
pending: this.tx('crud.authoring.section.status.pending', 'Needs attention'),
|
|
1901
|
+
invalid: this.tx('crud.authoring.section.status.invalid', 'Invalid'),
|
|
1902
|
+
}[this.sectionStatus(section)];
|
|
1903
|
+
}
|
|
1904
|
+
sectionSummary(section) {
|
|
1905
|
+
if (section === 'connection') {
|
|
1906
|
+
return this.connectionOverview();
|
|
1907
|
+
}
|
|
1908
|
+
if (section === 'defaults') {
|
|
1909
|
+
return this.defaultsOverview();
|
|
1910
|
+
}
|
|
1911
|
+
if (section === 'actions') {
|
|
1912
|
+
const readyCount = this.actionKeys.filter((actionName) => this.actionStatus(actionName) === 'ready').length;
|
|
1913
|
+
const invalidCount = this.actionKeys.filter((actionName) => this.actionStatus(actionName) === 'invalid').length;
|
|
1914
|
+
const focusedAction = this.prioritizedActionKey();
|
|
1915
|
+
if (invalidCount > 0) {
|
|
1916
|
+
return this.tx('crud.authoring.section.actions.invalidSummaryDetailed', '{count} action blocks have validation errors. Start with {action}.')
|
|
1917
|
+
.replace('{count}', String(invalidCount))
|
|
1918
|
+
.replace('{action}', focusedAction ? this.actionLabel(focusedAction) : this.actionLabel('create'));
|
|
1919
|
+
}
|
|
1920
|
+
if (readyCount === this.actionKeys.length) {
|
|
1921
|
+
return this.tx('crud.authoring.section.actions.readySummary', 'All primary CRUD actions are configured.');
|
|
1922
|
+
}
|
|
1923
|
+
return this.tx('crud.authoring.section.actions.pendingSummaryDetailed', '{count} of {total} action blocks are ready. Next: {action}.')
|
|
1924
|
+
.replace('{count}', String(readyCount))
|
|
1925
|
+
.replace('{total}', String(this.actionKeys.length))
|
|
1926
|
+
.replace('{action}', focusedAction ? this.actionLabel(focusedAction) : this.actionLabel('create'));
|
|
1927
|
+
}
|
|
1928
|
+
return this.tx('crud.authoring.table.sectionSummary', '{structure}. {behavior}. {states}.')
|
|
1929
|
+
.replace('{structure}', this.tableStructureSummary())
|
|
1930
|
+
.replace('{behavior}', this.tableBehaviorSummary())
|
|
1931
|
+
.replace('{states}', this.tableStatesSummary());
|
|
1932
|
+
}
|
|
1933
|
+
nextFocusTitle() {
|
|
1934
|
+
if (this.nextFocusStatus() === 'ready') {
|
|
1935
|
+
return this.tx('crud.authoring.nextFocus.completeTitle', 'Shell guidance complete');
|
|
1936
|
+
}
|
|
1937
|
+
return this.tx(`crud.authoring.nextFocus.${this.nextFocusSection()}`, this.nextFocusSection());
|
|
1938
|
+
}
|
|
1939
|
+
nextFocusSummary() {
|
|
1940
|
+
if (this.nextFocusStatus() === 'invalid') {
|
|
1941
|
+
if (this.nextFocusSection() === 'actions') {
|
|
1942
|
+
const focusedAction = this.prioritizedActionKey();
|
|
1943
|
+
if (focusedAction) {
|
|
1944
|
+
return this.tx('crud.authoring.nextFocus.actionsInvalid', 'Resolve {action} first. {summary}.')
|
|
1945
|
+
.replace('{action}', this.actionLabel(focusedAction))
|
|
1946
|
+
.replace('{summary}', this.actionFocusSummary(focusedAction));
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
return this.tx('crud.authoring.nextFocus.invalid', 'Resolve the blocking diagnostics in this section before saving with confidence.');
|
|
1950
|
+
}
|
|
1951
|
+
if (this.nextFocusStatus() === 'pending') {
|
|
1952
|
+
if (this.nextFocusSection() === 'actions') {
|
|
1953
|
+
const focusedAction = this.prioritizedActionKey();
|
|
1954
|
+
if (focusedAction) {
|
|
1955
|
+
return this.tx('crud.authoring.nextFocus.actionsPending', 'Open {action} next. {summary}.')
|
|
1956
|
+
.replace('{action}', this.actionLabel(focusedAction))
|
|
1957
|
+
.replace('{summary}', this.actionFocusSummary(focusedAction));
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
return this.tx('crud.authoring.nextFocus.pending', 'This section is the next best place to complete the canonical CRUD flow.');
|
|
1961
|
+
}
|
|
1962
|
+
return this.tx('crud.authoring.nextFocus.ready', 'All tracked sections are currently in a good state.');
|
|
1963
|
+
}
|
|
1964
|
+
nextFocusChipLabel() {
|
|
1965
|
+
if (this.nextFocusStatus() === 'ready') {
|
|
1966
|
+
return this.tx('crud.authoring.nextFocus.complete', 'Complete');
|
|
1967
|
+
}
|
|
1968
|
+
return this.sectionStatusLabel(this.nextFocusSection());
|
|
1969
|
+
}
|
|
1970
|
+
healthBucketLabel(bucket) {
|
|
1971
|
+
return {
|
|
1972
|
+
invalid: this.tx('crud.authoring.health.invalid', 'Invalid'),
|
|
1973
|
+
pending: this.tx('crud.authoring.health.pending', 'Needs attention'),
|
|
1974
|
+
ready: this.tx('crud.authoring.health.ready', 'Healthy'),
|
|
1975
|
+
}[bucket];
|
|
1976
|
+
}
|
|
1977
|
+
healthBucketSummary(bucket) {
|
|
1978
|
+
const sections = ['connection', 'defaults', 'actions', 'table']
|
|
1979
|
+
.filter((section) => this.sectionStatus(section) === bucket);
|
|
1980
|
+
if (!sections.length) {
|
|
1981
|
+
return this.tx(`crud.authoring.health.${bucket}Empty`, 'None');
|
|
1982
|
+
}
|
|
1983
|
+
return sections.map((section) => this.sectionDisplayLabel(section)).join(' · ');
|
|
1984
|
+
}
|
|
1985
|
+
diagnosticsGroupStatusLabel(section, issues) {
|
|
1986
|
+
const hasError = issues.some((issue) => issue.level === 'error');
|
|
1987
|
+
if (hasError) {
|
|
1988
|
+
return this.tx('crud.authoring.diagnostics.errors', 'Errors');
|
|
1989
|
+
}
|
|
1990
|
+
return this.tx('crud.authoring.diagnostics.warnings', 'Warnings');
|
|
1991
|
+
}
|
|
1992
|
+
diagnosticsGroupSummary(section, issues) {
|
|
1993
|
+
const errorCount = issues.filter((issue) => issue.level === 'error').length;
|
|
1994
|
+
const warningCount = issues.length - errorCount;
|
|
1995
|
+
if (errorCount > 0) {
|
|
1996
|
+
return this.tx('crud.authoring.diagnostics.errorSummary', '{count} blocking diagnostics in this section.')
|
|
1997
|
+
.replace('{count}', String(errorCount));
|
|
1998
|
+
}
|
|
1999
|
+
return this.tx('crud.authoring.diagnostics.warningSummary', '{count} diagnostics worth reviewing in this section.')
|
|
2000
|
+
.replace('{count}', String(warningCount));
|
|
2001
|
+
}
|
|
2002
|
+
modeLabel(mode) {
|
|
2003
|
+
return this.tx(`crud.authoring.mode.${mode}`, mode);
|
|
2004
|
+
}
|
|
2005
|
+
variantLabel(variant) {
|
|
2006
|
+
return this.tx(`crud.authoring.header.variant.${variant}`, variant);
|
|
2007
|
+
}
|
|
2008
|
+
modalDensityLabel(density) {
|
|
2009
|
+
return this.tx(`crud.authoring.defaults.modal.density.${density}`, density);
|
|
2010
|
+
}
|
|
2011
|
+
backStrategyLabel(strategy) {
|
|
2012
|
+
return this.tx(`crud.authoring.defaults.back.strategy.${strategy}`, strategy);
|
|
2013
|
+
}
|
|
2014
|
+
actionInputsSummaryBadge(actionName) {
|
|
2015
|
+
const paramsCount = this.actionParams(actionName).length;
|
|
2016
|
+
const hasInitialValue = !!this.actionValue(actionName).form?.initialValue;
|
|
2017
|
+
const parts = [];
|
|
2018
|
+
if (paramsCount > 0) {
|
|
2019
|
+
parts.push(this.tx('crud.authoring.action.paramsSummaryCompact', '{count} map')
|
|
2020
|
+
.replace('{count}', String(paramsCount)));
|
|
2021
|
+
}
|
|
2022
|
+
if (hasInitialValue) {
|
|
2023
|
+
parts.push(this.tx('crud.authoring.action.initialValueReadyCompact', 'Seed'));
|
|
2024
|
+
}
|
|
2025
|
+
return parts.join(' · ');
|
|
2026
|
+
}
|
|
2027
|
+
paramTargetLabel(target) {
|
|
2028
|
+
return this.tx(`crud.authoring.action.paramTarget.${target}`, target);
|
|
2029
|
+
}
|
|
2030
|
+
diagnosticMessage(code, fallback) {
|
|
2031
|
+
const key = {
|
|
2032
|
+
'crud.metadata.actions.route.required': 'crud.authoring.validation.routeRequired',
|
|
2033
|
+
'crud.metadata.actions.formId.required': 'crud.authoring.validation.formIdRequired',
|
|
2034
|
+
'crud.metadata.actions.form.submit-contract.invalid': 'crud.authoring.validation.submitContract',
|
|
2035
|
+
}[code];
|
|
2036
|
+
return key ? this.tx(key, fallback) : fallback;
|
|
2037
|
+
}
|
|
2038
|
+
setResourceField(field, value) {
|
|
2039
|
+
this.updateMetadata((metadata) => ({
|
|
2040
|
+
...metadata,
|
|
2041
|
+
resource: {
|
|
2042
|
+
...(metadata.resource || { path: '' }),
|
|
2043
|
+
[field]: typeof value === 'string' ? value : value ?? undefined,
|
|
2044
|
+
},
|
|
2045
|
+
}));
|
|
2046
|
+
}
|
|
2047
|
+
setDefaultsOpenMode(value) {
|
|
2048
|
+
this.updateMetadata((metadata) => ({
|
|
2049
|
+
...metadata,
|
|
2050
|
+
defaults: {
|
|
2051
|
+
...(metadata.defaults || {}),
|
|
2052
|
+
openMode: value || 'route',
|
|
2053
|
+
},
|
|
2054
|
+
}));
|
|
2055
|
+
}
|
|
2056
|
+
setHeaderField(field, value) {
|
|
2057
|
+
this.updateMetadata((metadata) => ({
|
|
2058
|
+
...metadata,
|
|
2059
|
+
defaults: {
|
|
2060
|
+
...(metadata.defaults || {}),
|
|
2061
|
+
header: {
|
|
2062
|
+
...(metadata.defaults?.header || {}),
|
|
2063
|
+
[field]: value,
|
|
2064
|
+
},
|
|
2065
|
+
},
|
|
2066
|
+
}));
|
|
2067
|
+
}
|
|
2068
|
+
setModalField(field, value) {
|
|
2069
|
+
const nextValue = field === 'fullscreenBreakpoint'
|
|
2070
|
+
? this.normalizeOptionalNumber(value)
|
|
2071
|
+
: value;
|
|
2072
|
+
this.updateMetadata((metadata) => ({
|
|
2073
|
+
...metadata,
|
|
2074
|
+
defaults: {
|
|
2075
|
+
...(metadata.defaults || {}),
|
|
2076
|
+
modal: {
|
|
2077
|
+
...(metadata.defaults?.modal || {}),
|
|
2078
|
+
[field]: nextValue,
|
|
2079
|
+
},
|
|
2080
|
+
},
|
|
2081
|
+
}));
|
|
2082
|
+
}
|
|
2083
|
+
setBackField(field, value) {
|
|
2084
|
+
this.updateMetadata((metadata) => ({
|
|
2085
|
+
...metadata,
|
|
2086
|
+
defaults: {
|
|
2087
|
+
...(metadata.defaults || {}),
|
|
2088
|
+
back: {
|
|
2089
|
+
...(metadata.defaults?.back || {}),
|
|
2090
|
+
[field]: typeof value === 'string' ? value : value ?? undefined,
|
|
2091
|
+
},
|
|
2092
|
+
},
|
|
2093
|
+
}));
|
|
2094
|
+
}
|
|
2095
|
+
setActionField(actionName, field, value) {
|
|
2096
|
+
this.patchAction(actionName, (action) => ({
|
|
2097
|
+
...action,
|
|
2098
|
+
[field]: typeof value === 'string' ? value : value ?? undefined,
|
|
2099
|
+
}));
|
|
2100
|
+
}
|
|
2101
|
+
setActionFormField(actionName, field, value) {
|
|
2102
|
+
this.patchAction(actionName, (action) => ({
|
|
2103
|
+
...action,
|
|
2104
|
+
form: {
|
|
2105
|
+
...(action.form || {}),
|
|
2106
|
+
[field]: typeof value === 'string' ? value : value ?? undefined,
|
|
2107
|
+
},
|
|
2108
|
+
}));
|
|
2109
|
+
}
|
|
2110
|
+
addActionParam(actionName) {
|
|
2111
|
+
this.patchAction(actionName, (action) => ({
|
|
2112
|
+
...action,
|
|
2113
|
+
params: [...(action.params || []), { from: '', to: 'input', name: '' }],
|
|
2114
|
+
}));
|
|
2115
|
+
}
|
|
2116
|
+
removeActionParam(actionName, index) {
|
|
2117
|
+
this.patchAction(actionName, (action) => {
|
|
2118
|
+
const params = [...(action.params || [])];
|
|
2119
|
+
params.splice(index, 1);
|
|
2120
|
+
return {
|
|
2121
|
+
...action,
|
|
2122
|
+
params: params.length ? params : undefined,
|
|
2123
|
+
};
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
setActionParamField(actionName, index, field, value) {
|
|
2127
|
+
this.patchAction(actionName, (action) => {
|
|
2128
|
+
const params = [...(action.params || [])];
|
|
2129
|
+
const current = params[index] || { from: '', to: 'input', name: '' };
|
|
2130
|
+
params[index] = {
|
|
2131
|
+
...current,
|
|
2132
|
+
[field]: typeof value === 'string' ? value : value ?? undefined,
|
|
2133
|
+
};
|
|
2134
|
+
return {
|
|
2135
|
+
...action,
|
|
2136
|
+
params,
|
|
2137
|
+
};
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
setActionInitialValue(actionName, raw) {
|
|
2141
|
+
this.initialValueDrafts.update((drafts) => ({
|
|
2142
|
+
...drafts,
|
|
2143
|
+
[actionName]: raw,
|
|
2144
|
+
}));
|
|
2145
|
+
const trimmed = raw.trim();
|
|
2146
|
+
if (!trimmed) {
|
|
2147
|
+
this.patchAction(actionName, (action) => ({
|
|
2148
|
+
...action,
|
|
2149
|
+
form: {
|
|
2150
|
+
...(action.form || {}),
|
|
2151
|
+
initialValue: undefined,
|
|
2152
|
+
},
|
|
2153
|
+
}));
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
try {
|
|
2157
|
+
const parsed = JSON.parse(trimmed);
|
|
2158
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
this.patchAction(actionName, (action) => ({
|
|
2162
|
+
...action,
|
|
2163
|
+
form: {
|
|
2164
|
+
...(action.form || {}),
|
|
2165
|
+
initialValue: parsed,
|
|
2166
|
+
},
|
|
2167
|
+
}));
|
|
2168
|
+
}
|
|
2169
|
+
catch {
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
setActionBackField(actionName, field, value) {
|
|
2174
|
+
this.patchAction(actionName, (action) => ({
|
|
2175
|
+
...action,
|
|
2176
|
+
back: {
|
|
2177
|
+
...(action.back || {}),
|
|
2178
|
+
[field]: typeof value === 'string' ? value || undefined : value ?? undefined,
|
|
2179
|
+
},
|
|
2180
|
+
}));
|
|
2181
|
+
}
|
|
2182
|
+
setActionApiUrlEntry(actionName, raw) {
|
|
2183
|
+
let parsed = undefined;
|
|
2184
|
+
const trimmed = raw.trim();
|
|
2185
|
+
if (trimmed) {
|
|
2186
|
+
try {
|
|
2187
|
+
parsed = JSON.parse(trimmed);
|
|
2188
|
+
}
|
|
2189
|
+
catch {
|
|
2190
|
+
parsed = trimmed;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
this.patchAction(actionName, (action) => ({
|
|
2194
|
+
...action,
|
|
2195
|
+
form: {
|
|
2196
|
+
...(action.form || {}),
|
|
2197
|
+
apiUrlEntry: parsed,
|
|
2198
|
+
},
|
|
2199
|
+
}));
|
|
2200
|
+
}
|
|
2201
|
+
setTableConfig(config) {
|
|
2202
|
+
this.updateMetadata((metadata) => ({
|
|
2203
|
+
...metadata,
|
|
2204
|
+
table: cloneTableConfig(config),
|
|
2205
|
+
}));
|
|
2206
|
+
}
|
|
2207
|
+
patchAction(actionName, patcher) {
|
|
2208
|
+
this.updateMetadata((metadata) => {
|
|
2209
|
+
const actions = [...(metadata.actions || [])];
|
|
2210
|
+
const index = actions.findIndex((entry) => entry?.action === actionName);
|
|
2211
|
+
const current = index >= 0 ? actions[index] : { action: actionName, label: this.actionLabel(actionName) };
|
|
2212
|
+
const next = patcher(current);
|
|
2213
|
+
if (index >= 0) {
|
|
2214
|
+
actions[index] = next;
|
|
2215
|
+
}
|
|
2216
|
+
else {
|
|
2217
|
+
actions.push(next);
|
|
2218
|
+
}
|
|
2219
|
+
return { ...metadata, actions };
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
updateMetadata(updater) {
|
|
2223
|
+
const next = normalizeCrudAuthoringDocument({
|
|
2224
|
+
...this.currentDocument(),
|
|
2225
|
+
metadata: updater(this.currentDocument().metadata),
|
|
2226
|
+
});
|
|
2227
|
+
this.currentDocument.set(next);
|
|
2228
|
+
this.syncState();
|
|
2229
|
+
}
|
|
2230
|
+
buildPayload() {
|
|
2231
|
+
const document = normalizeCrudAuthoringDocument(this.currentDocument());
|
|
2232
|
+
return {
|
|
2233
|
+
document,
|
|
2234
|
+
metadata: document.metadata,
|
|
2235
|
+
id: this.effectiveCrudId(),
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
resolveExternalSeed() {
|
|
2239
|
+
return this.documentInput()
|
|
2240
|
+
|| this.metadataInput()
|
|
2241
|
+
|| this.injectedData?.document
|
|
2242
|
+
|| this.injectedData?.metadata
|
|
2243
|
+
|| null;
|
|
2244
|
+
}
|
|
2245
|
+
syncState() {
|
|
2246
|
+
const currentSignature = JSON.stringify(serializeCrudAuthoringDocument(this.currentDocument()));
|
|
2247
|
+
const initialSignature = JSON.stringify(serializeCrudAuthoringDocument(this.initialDocument()));
|
|
2248
|
+
this.isDirty$.next(currentSignature !== initialSignature);
|
|
2249
|
+
this.isValid$.next(!this.diagnostics().some((issue) => issue.level === 'error'));
|
|
2250
|
+
}
|
|
2251
|
+
tx(key, fallback) {
|
|
2252
|
+
return translateCrudAuthoringText(this.i18n, key, fallback);
|
|
2253
|
+
}
|
|
2254
|
+
normalizeOptionalNumber(value) {
|
|
2255
|
+
const trimmed = String(value ?? '').trim();
|
|
2256
|
+
if (!trimmed) {
|
|
2257
|
+
return undefined;
|
|
2258
|
+
}
|
|
2259
|
+
const parsed = Number(trimmed);
|
|
2260
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
2261
|
+
}
|
|
2262
|
+
hasHealthBucketContent(bucket) {
|
|
2263
|
+
return ['connection', 'defaults', 'actions', 'table']
|
|
2264
|
+
.some((section) => this.sectionStatus(section) === bucket);
|
|
2265
|
+
}
|
|
2266
|
+
sectionDiagnostics(section) {
|
|
2267
|
+
const prefix = {
|
|
2268
|
+
connection: 'metadata.resource',
|
|
2269
|
+
defaults: 'metadata.defaults',
|
|
2270
|
+
actions: 'metadata.actions',
|
|
2271
|
+
table: 'metadata.table',
|
|
2272
|
+
}[section];
|
|
2273
|
+
return this.diagnostics().filter((issue) => String(issue.path || '').includes(prefix));
|
|
2274
|
+
}
|
|
2275
|
+
actionDiagnostics(actionName) {
|
|
2276
|
+
return this.diagnostics()
|
|
2277
|
+
.filter((issue) => String(issue.path || '').includes(`metadata.actions.${actionName}`));
|
|
2278
|
+
}
|
|
2279
|
+
actionGroupLabelsByStatus(actionName, status) {
|
|
2280
|
+
return ['binding', 'schema', 'submit', 'api']
|
|
2281
|
+
.filter((group) => this.actionGroupStatus(actionName, group) === status)
|
|
2282
|
+
.map((group) => this.actionGroupLabel(group));
|
|
2283
|
+
}
|
|
2284
|
+
actionFocusSummary(actionName) {
|
|
2285
|
+
const invalidGroups = this.actionGroupLabelsByStatus(actionName, 'invalid');
|
|
2286
|
+
if (invalidGroups.length) {
|
|
2287
|
+
return this.tx('crud.authoring.action.focusInvalid', '{count} blocking groups')
|
|
2288
|
+
.replace('{count}', String(invalidGroups.length));
|
|
2289
|
+
}
|
|
2290
|
+
const pendingGroups = this.actionGroupLabelsByStatus(actionName, 'pending');
|
|
2291
|
+
if (pendingGroups.length) {
|
|
2292
|
+
return this.tx('crud.authoring.action.focusPending', '{count} groups still pending')
|
|
2293
|
+
.replace('{count}', String(pendingGroups.length));
|
|
2294
|
+
}
|
|
2295
|
+
return this.tx('crud.authoring.action.focusReady', 'Ready');
|
|
2296
|
+
}
|
|
2297
|
+
prioritizedActionKey() {
|
|
2298
|
+
return this.actionKeys.find((actionName) => this.actionStatus(actionName) === 'invalid')
|
|
2299
|
+
|| this.actionKeys.find((actionName) => this.actionStatus(actionName) === 'pending')
|
|
2300
|
+
|| null;
|
|
2301
|
+
}
|
|
2302
|
+
sectionDisplayLabel(section) {
|
|
2303
|
+
return {
|
|
2304
|
+
connection: this.tx('crud.authoring.section.connection', 'Connection'),
|
|
2305
|
+
defaults: this.tx('crud.authoring.section.defaults', 'Open mode and header'),
|
|
2306
|
+
actions: this.tx('crud.authoring.section.actions', 'Actions'),
|
|
2307
|
+
table: this.tx('crud.authoring.section.table', 'Table'),
|
|
2308
|
+
}[section];
|
|
2309
|
+
}
|
|
2310
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2311
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: CrudMetadataEditorComponent, isStandalone: true, selector: "praxis-crud-metadata-editor", inputs: { documentInput: { classPropertyName: "documentInput", publicName: "document", isSignal: true, isRequired: false, transformFunction: null }, metadataInput: { classPropertyName: "metadataInput", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, crudIdInput: { classPropertyName: "crudIdInput", publicName: "crudId", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, providers: [providePraxisI18nConfig(PRAXIS_CRUD_AUTHORING_I18N_CONFIG)], ngImport: i0, template: `
|
|
2312
|
+
<section class="editor-shell" data-testid="crud-metadata-editor">
|
|
2313
|
+
<header class="editor-header">
|
|
2314
|
+
<div>
|
|
2315
|
+
<h2>{{ tx('crud.authoring.title', 'CRUD settings') }}</h2>
|
|
2316
|
+
<p>{{ tx('crud.authoring.subtitle', 'Edit the canonical CRUD flow document.') }}</p>
|
|
2317
|
+
</div>
|
|
2318
|
+
<span class="editor-chip" data-testid="crud-metadata-editor-id">{{ effectiveCrudId() || 'lab' }}</span>
|
|
2319
|
+
</header>
|
|
2320
|
+
|
|
2321
|
+
<section class="editor-overview" data-testid="crud-editor-overview">
|
|
2322
|
+
<article class="editor-overview-card">
|
|
2323
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.connection', 'Connection') }}</span>
|
|
2324
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-connection">{{ connectionOverview() }}</strong>
|
|
2325
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-connection-note">{{ connectionOverviewNote() }}</p>
|
|
2326
|
+
</article>
|
|
2327
|
+
<article class="editor-overview-card">
|
|
2328
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.defaults', 'Defaults') }}</span>
|
|
2329
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-defaults">{{ defaultsOverview() }}</strong>
|
|
2330
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-defaults-note">{{ defaultsOverviewNote() }}</p>
|
|
2331
|
+
</article>
|
|
2332
|
+
<article class="editor-overview-card">
|
|
2333
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.actions', 'Actions') }}</span>
|
|
2334
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-actions">{{ actionsOverview() }}</strong>
|
|
2335
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-actions-note">{{ actionsOverviewNote() }}</p>
|
|
2336
|
+
</article>
|
|
2337
|
+
<article class="editor-overview-card">
|
|
2338
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.table', 'Table') }}</span>
|
|
2339
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-table">{{ tableOverview() }}</strong>
|
|
2340
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-table-note">{{ tableOverviewNote() }}</p>
|
|
2341
|
+
</article>
|
|
2342
|
+
<article class="editor-overview-card" [class.editor-overview-card--error]="errorCount() > 0" [class.editor-overview-card--warn]="errorCount() === 0 && warningCount() > 0">
|
|
2343
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.validation', 'Validation') }}</span>
|
|
2344
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-validation">{{ validationOverview() }}</strong>
|
|
2345
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-validation-note">{{ validationOverviewNote() }}</p>
|
|
2346
|
+
</article>
|
|
2347
|
+
</section>
|
|
2348
|
+
|
|
2349
|
+
@if (showHealthMap()) {
|
|
2350
|
+
<section class="editor-health-map" data-testid="crud-editor-health-map">
|
|
2351
|
+
@for (bucket of visibleHealthBuckets(); track bucket) {
|
|
2352
|
+
<article
|
|
2353
|
+
class="editor-health-card"
|
|
2354
|
+
[class.editor-health-card--error]="bucket === 'invalid'"
|
|
2355
|
+
[class.editor-health-card--warn]="bucket === 'pending'"
|
|
2356
|
+
[attr.data-testid]="'crud-editor-health-' + bucket"
|
|
2357
|
+
>
|
|
2358
|
+
<span class="editor-overview-label">{{ healthBucketLabel(bucket) }}</span>
|
|
2359
|
+
<strong>{{ healthBucketSummary(bucket) }}</strong>
|
|
2360
|
+
</article>
|
|
2361
|
+
}
|
|
2362
|
+
</section>
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
<section class="editor-focus" data-testid="crud-editor-next-focus" [class.editor-focus--success]="nextFocusStatus() === 'ready'">
|
|
2366
|
+
<span
|
|
2367
|
+
class="editor-focus-chip"
|
|
2368
|
+
[class.editor-focus-chip--error]="nextFocusStatus() === 'invalid'"
|
|
2369
|
+
[class.editor-focus-chip--warn]="nextFocusStatus() === 'pending'"
|
|
2370
|
+
>
|
|
2371
|
+
{{ nextFocusChipLabel() }}
|
|
2372
|
+
</span>
|
|
2373
|
+
<div class="editor-focus-copy">
|
|
2374
|
+
<strong>{{ nextFocusTitle() }}</strong>
|
|
2375
|
+
<p>{{ nextFocusSummary() }}</p>
|
|
2376
|
+
</div>
|
|
2377
|
+
</section>
|
|
2378
|
+
|
|
2379
|
+
<mat-card class="editor-card">
|
|
2380
|
+
<div class="editor-section-header">
|
|
2381
|
+
<div>
|
|
2382
|
+
<h3>{{ tx('crud.authoring.section.connection', 'Connection') }}</h3>
|
|
2383
|
+
<p class="editor-section-note">{{ sectionSummary('connection') }}</p>
|
|
2384
|
+
</div>
|
|
2385
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="sectionStatus('connection') === 'invalid'" [class.action-summary-chip--warn]="sectionStatus('connection') === 'pending'">
|
|
2386
|
+
{{ sectionStatusLabel('connection') }}
|
|
2387
|
+
</span>
|
|
2388
|
+
</div>
|
|
2389
|
+
<div class="editor-grid">
|
|
2390
|
+
<mat-form-field appearance="outline">
|
|
2391
|
+
<mat-label>{{ tx('crud.authoring.connection.resourcePath', 'Resource') }}</mat-label>
|
|
2392
|
+
<input matInput data-testid="crud-editor-resource-path" [ngModel]="resourcePath()" (ngModelChange)="setResourceField('path', $event)" [disabled]="isReadonly()" />
|
|
2393
|
+
</mat-form-field>
|
|
2394
|
+
<mat-form-field appearance="outline">
|
|
2395
|
+
<mat-label>{{ tx('crud.authoring.connection.idField', 'Identifier field') }}</mat-label>
|
|
2396
|
+
<input matInput data-testid="crud-editor-resource-idField" [ngModel]="resourceIdField()" (ngModelChange)="setResourceField('idField', $event)" [disabled]="isReadonly()" />
|
|
2397
|
+
</mat-form-field>
|
|
2398
|
+
<mat-form-field appearance="outline">
|
|
2399
|
+
<mat-label>{{ tx('crud.authoring.connection.endpointKey', 'Endpoint/API') }}</mat-label>
|
|
2400
|
+
<input matInput data-testid="crud-editor-resource-endpointKey" [ngModel]="resourceEndpointKey()" (ngModelChange)="setResourceField('endpointKey', $event)" [disabled]="isReadonly()" />
|
|
2401
|
+
</mat-form-field>
|
|
2402
|
+
</div>
|
|
2403
|
+
</mat-card>
|
|
2404
|
+
|
|
2405
|
+
<mat-card class="editor-card">
|
|
2406
|
+
<div class="editor-section-header">
|
|
2407
|
+
<div>
|
|
2408
|
+
<h3>{{ tx('crud.authoring.section.defaults', 'Open mode and header') }}</h3>
|
|
2409
|
+
<p class="editor-section-note">{{ sectionSummary('defaults') }}</p>
|
|
2410
|
+
</div>
|
|
2411
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="sectionStatus('defaults') === 'invalid'" [class.action-summary-chip--warn]="sectionStatus('defaults') === 'pending'">
|
|
2412
|
+
{{ sectionStatusLabel('defaults') }}
|
|
2413
|
+
</span>
|
|
2414
|
+
</div>
|
|
2415
|
+
<div class="editor-grid">
|
|
2416
|
+
<mat-form-field appearance="outline">
|
|
2417
|
+
<mat-label>{{ tx('crud.authoring.defaults.openMode', 'Default open mode') }}</mat-label>
|
|
2418
|
+
<mat-select data-testid="crud-editor-defaults-openMode" [ngModel]="defaultsOpenMode()" (ngModelChange)="setDefaultsOpenMode($event)" [disabled]="isReadonly()">
|
|
2419
|
+
@for (mode of openModes; track mode) {
|
|
2420
|
+
<mat-option [value]="mode">{{ modeLabel(mode) }}</mat-option>
|
|
2421
|
+
}
|
|
2422
|
+
</mat-select>
|
|
2423
|
+
</mat-form-field>
|
|
2424
|
+
<mat-form-field appearance="outline">
|
|
2425
|
+
<mat-label>{{ tx('crud.authoring.defaults.header.backLabel', 'Back button label') }}</mat-label>
|
|
2426
|
+
<input matInput data-testid="crud-editor-header-backLabel" [ngModel]="headerBackLabel()" (ngModelChange)="setHeaderField('backLabel', $event)" [disabled]="isReadonly()" />
|
|
2427
|
+
</mat-form-field>
|
|
2428
|
+
<mat-form-field appearance="outline">
|
|
2429
|
+
<mat-label>{{ tx('crud.authoring.defaults.header.variant', 'Button variant') }}</mat-label>
|
|
2430
|
+
<mat-select data-testid="crud-editor-header-variant" [ngModel]="headerVariant()" (ngModelChange)="setHeaderField('variant', $event)" [disabled]="isReadonly()">
|
|
2431
|
+
@for (variant of headerVariants; track variant) {
|
|
2432
|
+
<mat-option [value]="variant">{{ variantLabel(variant) }}</mat-option>
|
|
2433
|
+
}
|
|
2434
|
+
</mat-select>
|
|
2435
|
+
</mat-form-field>
|
|
2436
|
+
</div>
|
|
2437
|
+
<div class="editor-toggles">
|
|
2438
|
+
<mat-slide-toggle data-testid="crud-editor-header-showBack" [ngModel]="headerShowBack()" (ngModelChange)="setHeaderField('showBack', $event)" [disabled]="isReadonly()">
|
|
2439
|
+
{{ tx('crud.authoring.defaults.header.showBack', 'Show back button') }}
|
|
2440
|
+
</mat-slide-toggle>
|
|
2441
|
+
<mat-slide-toggle data-testid="crud-editor-header-sticky" [ngModel]="headerSticky()" (ngModelChange)="setHeaderField('sticky', $event)" [disabled]="isReadonly()">
|
|
2442
|
+
{{ tx('crud.authoring.defaults.header.sticky', 'Sticky header') }}
|
|
2443
|
+
</mat-slide-toggle>
|
|
2444
|
+
<mat-slide-toggle data-testid="crud-editor-header-breadcrumbs" [ngModel]="headerBreadcrumbs()" (ngModelChange)="setHeaderField('breadcrumbs', $event)" [disabled]="isReadonly()">
|
|
2445
|
+
{{ tx('crud.authoring.defaults.header.breadcrumbs', 'Show breadcrumbs') }}
|
|
2446
|
+
</mat-slide-toggle>
|
|
2447
|
+
<mat-slide-toggle data-testid="crud-editor-header-divider" [ngModel]="headerDivider()" (ngModelChange)="setHeaderField('divider', $event)" [disabled]="isReadonly()">
|
|
2448
|
+
{{ tx('crud.authoring.defaults.header.divider', 'Show bottom divider') }}
|
|
2449
|
+
</mat-slide-toggle>
|
|
2450
|
+
</div>
|
|
2451
|
+
<section class="action-group" data-testid="crud-editor-defaults-modal-group">
|
|
2452
|
+
<div class="action-group__header">
|
|
2453
|
+
<div>
|
|
2454
|
+
<strong>{{ tx('crud.authoring.defaults.modal.group', 'Modal presentation') }}</strong>
|
|
2455
|
+
<p class="editor-section-note">{{ modalDefaultsNote() }}</p>
|
|
2456
|
+
</div>
|
|
2457
|
+
</div>
|
|
2458
|
+
<div class="editor-grid">
|
|
2459
|
+
<mat-form-field appearance="outline">
|
|
2460
|
+
<mat-label>{{ tx('crud.authoring.defaults.modal.density', 'Modal density') }}</mat-label>
|
|
2461
|
+
<mat-select data-testid="crud-editor-modal-density" [ngModel]="modalDensity()" (ngModelChange)="setModalField('density', $event)" [disabled]="isReadonly()">
|
|
2462
|
+
@for (density of modalDensities; track density) {
|
|
2463
|
+
<mat-option [value]="density">{{ modalDensityLabel(density) }}</mat-option>
|
|
2464
|
+
}
|
|
2465
|
+
</mat-select>
|
|
2466
|
+
</mat-form-field>
|
|
2467
|
+
<mat-form-field appearance="outline">
|
|
2468
|
+
<mat-label>{{ tx('crud.authoring.defaults.modal.fullscreenBreakpoint', 'Fullscreen breakpoint') }}</mat-label>
|
|
2469
|
+
<input matInput data-testid="crud-editor-modal-fullscreenBreakpoint" [ngModel]="modalFullscreenBreakpoint()" (ngModelChange)="setModalField('fullscreenBreakpoint', $event)" [disabled]="isReadonly()" />
|
|
2470
|
+
</mat-form-field>
|
|
2471
|
+
</div>
|
|
2472
|
+
<div class="editor-toggles">
|
|
2473
|
+
<mat-slide-toggle data-testid="crud-editor-modal-canMaximize" [ngModel]="modalCanMaximize()" (ngModelChange)="setModalField('canMaximize', $event)" [disabled]="isReadonly()">
|
|
2474
|
+
{{ tx('crud.authoring.defaults.modal.canMaximize', 'Show maximize control') }}
|
|
2475
|
+
</mat-slide-toggle>
|
|
2476
|
+
<mat-slide-toggle data-testid="crud-editor-modal-startMaximized" [ngModel]="modalStartMaximized()" (ngModelChange)="setModalField('startMaximized', $event)" [disabled]="isReadonly()">
|
|
2477
|
+
{{ tx('crud.authoring.defaults.modal.startMaximized', 'Start maximized') }}
|
|
2478
|
+
</mat-slide-toggle>
|
|
2479
|
+
<mat-slide-toggle data-testid="crud-editor-modal-rememberLastState" [ngModel]="modalRememberLastState()" (ngModelChange)="setModalField('rememberLastState', $event)" [disabled]="isReadonly()">
|
|
2480
|
+
{{ tx('crud.authoring.defaults.modal.rememberLastState', 'Remember last modal size') }}
|
|
2481
|
+
</mat-slide-toggle>
|
|
2482
|
+
<mat-slide-toggle data-testid="crud-editor-modal-disableCloseOnEsc" [ngModel]="modalDisableCloseOnEsc()" (ngModelChange)="setModalField('disableCloseOnEsc', $event)" [disabled]="isReadonly()">
|
|
2483
|
+
{{ tx('crud.authoring.defaults.modal.disableCloseOnEsc', 'Block close on Escape') }}
|
|
2484
|
+
</mat-slide-toggle>
|
|
2485
|
+
<mat-slide-toggle data-testid="crud-editor-modal-disableCloseOnBackdrop" [ngModel]="modalDisableCloseOnBackdrop()" (ngModelChange)="setModalField('disableCloseOnBackdrop', $event)" [disabled]="isReadonly()">
|
|
2486
|
+
{{ tx('crud.authoring.defaults.modal.disableCloseOnBackdrop', 'Block close on backdrop') }}
|
|
2487
|
+
</mat-slide-toggle>
|
|
2488
|
+
</div>
|
|
2489
|
+
</section>
|
|
2490
|
+
<section class="action-group" data-testid="crud-editor-defaults-back-group">
|
|
2491
|
+
<div class="action-group__header">
|
|
2492
|
+
<div>
|
|
2493
|
+
<strong>{{ tx('crud.authoring.defaults.back.group', 'Back behavior') }}</strong>
|
|
2494
|
+
<p class="editor-section-note">{{ backDefaultsNote() }}</p>
|
|
2495
|
+
</div>
|
|
2496
|
+
</div>
|
|
2497
|
+
<div class="editor-grid">
|
|
2498
|
+
<mat-form-field appearance="outline">
|
|
2499
|
+
<mat-label>{{ tx('crud.authoring.defaults.back.strategy', 'Back strategy') }}</mat-label>
|
|
2500
|
+
<mat-select data-testid="crud-editor-back-strategy" [ngModel]="backStrategy()" (ngModelChange)="setBackField('strategy', $event)" [disabled]="isReadonly()">
|
|
2501
|
+
@for (strategy of backStrategies; track strategy) {
|
|
2502
|
+
<mat-option [value]="strategy">{{ backStrategyLabel(strategy) }}</mat-option>
|
|
2503
|
+
}
|
|
2504
|
+
</mat-select>
|
|
2505
|
+
</mat-form-field>
|
|
2506
|
+
<mat-form-field appearance="outline">
|
|
2507
|
+
<mat-label>{{ tx('crud.authoring.defaults.back.returnTo', 'Return route') }}</mat-label>
|
|
2508
|
+
<input matInput data-testid="crud-editor-back-returnTo" [ngModel]="backReturnTo()" (ngModelChange)="setBackField('returnTo', $event)" [disabled]="isReadonly()" />
|
|
2509
|
+
</mat-form-field>
|
|
2510
|
+
</div>
|
|
2511
|
+
<div class="editor-toggles">
|
|
2512
|
+
<mat-slide-toggle data-testid="crud-editor-back-confirmOnDirty" [ngModel]="backConfirmOnDirty()" (ngModelChange)="setBackField('confirmOnDirty', $event)" [disabled]="isReadonly()">
|
|
2513
|
+
{{ tx('crud.authoring.defaults.back.confirmOnDirty', 'Confirm when leaving dirty forms') }}
|
|
2514
|
+
</mat-slide-toggle>
|
|
2515
|
+
</div>
|
|
2516
|
+
</section>
|
|
2517
|
+
</mat-card>
|
|
2518
|
+
|
|
2519
|
+
<section class="editor-actions">
|
|
2520
|
+
<div class="editor-section-header">
|
|
2521
|
+
<div>
|
|
2522
|
+
<h3>{{ tx('crud.authoring.section.actions', 'Actions') }}</h3>
|
|
2523
|
+
<p class="editor-section-note">{{ sectionSummary('actions') }}</p>
|
|
2524
|
+
</div>
|
|
2525
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="sectionStatus('actions') === 'invalid'" [class.action-summary-chip--warn]="sectionStatus('actions') === 'pending'">
|
|
2526
|
+
{{ sectionStatusLabel('actions') }}
|
|
2527
|
+
</span>
|
|
2528
|
+
</div>
|
|
2529
|
+
<div class="action-grid">
|
|
2530
|
+
@for (actionName of actionKeys; track actionName) {
|
|
2531
|
+
<mat-expansion-panel [expanded]="actionPanelExpanded(actionName)" class="editor-card" [attr.data-testid]="'crud-editor-action-' + actionName + '-panel'">
|
|
2532
|
+
<mat-expansion-panel-header [attr.data-testid]="'crud-editor-action-' + actionName + '-panel-header'">
|
|
2533
|
+
<mat-panel-title>{{ actionLabel(actionName) }}</mat-panel-title>
|
|
2534
|
+
<mat-panel-description [attr.data-testid]="'crud-editor-action-' + actionName + '-header-summary'">{{ actionSummary(actionName) }}</mat-panel-description>
|
|
2535
|
+
</mat-expansion-panel-header>
|
|
2536
|
+
<section class="action-summary-strip" [attr.data-testid]="'crud-editor-action-' + actionName + '-summary'">
|
|
2537
|
+
@if (actionShowStatusChip(actionName)) {
|
|
2538
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionStatus(actionName) === 'invalid'" [class.action-summary-chip--warn]="actionStatus(actionName) === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-status-chip'">
|
|
2539
|
+
{{ actionStatusLabel(actionName) }}
|
|
2540
|
+
</span>
|
|
2541
|
+
}
|
|
2542
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionStatus(actionName) === 'invalid'" [class.action-summary-chip--warn]="actionStatus(actionName) !== 'ready'" [attr.data-testid]="'crud-editor-action-' + actionName + '-primary-summary'">
|
|
2543
|
+
{{ actionPrimarySummary(actionName) }}
|
|
2544
|
+
</span>
|
|
2545
|
+
<span class="action-summary-chip" [attr.data-testid]="'crud-editor-action-' + actionName + '-advanced-summary'">{{ actionAdvancedSummary(actionName) }}</span>
|
|
2546
|
+
</section>
|
|
2547
|
+
<section class="action-group" [attr.data-testid]="'crud-editor-action-' + actionName + '-binding-group'">
|
|
2548
|
+
<div class="action-group__header">
|
|
2549
|
+
<div>
|
|
2550
|
+
<strong>{{ tx('crud.authoring.action.bindingGroup', 'Binding') }}</strong>
|
|
2551
|
+
<p class="editor-section-note">{{ actionBindingNote(actionName) }}</p>
|
|
2552
|
+
</div>
|
|
2553
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionGroupStatus(actionName, 'binding') === 'invalid'" [class.action-summary-chip--warn]="actionGroupStatus(actionName, 'binding') === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-binding-status'">
|
|
2554
|
+
{{ actionGroupStatusLabel(actionName, 'binding') }}
|
|
2555
|
+
</span>
|
|
2556
|
+
</div>
|
|
2557
|
+
<div class="editor-grid">
|
|
2558
|
+
<mat-form-field appearance="outline">
|
|
2559
|
+
<mat-label>{{ tx('crud.authoring.action.mode', 'Open mode') }}</mat-label>
|
|
2560
|
+
<mat-select [attr.data-testid]="'crud-editor-action-' + actionName + '-openMode'" [ngModel]="actionValue(actionName).openMode || ''" (ngModelChange)="setActionField(actionName, 'openMode', $event)" [disabled]="isReadonly()">
|
|
2561
|
+
<mat-option value="">Default</mat-option>
|
|
2562
|
+
@for (mode of openModes; track mode) {
|
|
2563
|
+
<mat-option [value]="mode">{{ modeLabel(mode) }}</mat-option>
|
|
2564
|
+
}
|
|
2565
|
+
</mat-select>
|
|
2566
|
+
</mat-form-field>
|
|
2567
|
+
<mat-form-field appearance="outline">
|
|
2568
|
+
<mat-label>{{ tx('crud.authoring.action.route', 'Route') }}</mat-label>
|
|
2569
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-route'" [ngModel]="actionValue(actionName).route || ''" (ngModelChange)="setActionField(actionName, 'route', $event)" [disabled]="isReadonly()" />
|
|
2570
|
+
</mat-form-field>
|
|
2571
|
+
<mat-form-field appearance="outline">
|
|
2572
|
+
<mat-label>{{ tx('crud.authoring.action.formId', 'formId') }}</mat-label>
|
|
2573
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-formId'" [ngModel]="actionValue(actionName).formId || ''" (ngModelChange)="setActionField(actionName, 'formId', $event)" [disabled]="isReadonly()" />
|
|
2574
|
+
</mat-form-field>
|
|
2575
|
+
</div>
|
|
2576
|
+
</section>
|
|
2577
|
+
<section class="action-group" [attr.data-testid]="'crud-editor-action-' + actionName + '-schema-group'">
|
|
2578
|
+
<div class="action-group__header">
|
|
2579
|
+
<div>
|
|
2580
|
+
<strong>{{ tx('crud.authoring.action.schemaGroup', 'Schema contract') }}</strong>
|
|
2581
|
+
<p class="editor-section-note">{{ actionSchemaNote(actionName) }}</p>
|
|
2582
|
+
</div>
|
|
2583
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionGroupStatus(actionName, 'schema') === 'invalid'" [class.action-summary-chip--warn]="actionGroupStatus(actionName, 'schema') === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-schema-status'">
|
|
2584
|
+
{{ actionGroupStatusLabel(actionName, 'schema') }}
|
|
2585
|
+
</span>
|
|
2586
|
+
</div>
|
|
2587
|
+
<div class="editor-grid">
|
|
2588
|
+
<mat-form-field appearance="outline">
|
|
2589
|
+
<mat-label>{{ tx('crud.authoring.action.schemaUrl', 'Schema URL') }}</mat-label>
|
|
2590
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-schemaUrl'" [ngModel]="actionValue(actionName).form?.schemaUrl || ''" (ngModelChange)="setActionFormField(actionName, 'schemaUrl', $event)" [disabled]="isReadonly()" />
|
|
2591
|
+
</mat-form-field>
|
|
2592
|
+
</div>
|
|
2593
|
+
</section>
|
|
2594
|
+
<mat-expansion-panel class="action-advanced-panel" [expanded]="actionAdvancedPanelExpanded(actionName)" [attr.data-testid]="'crud-editor-action-' + actionName + '-advanced-panel'">
|
|
2595
|
+
<mat-expansion-panel-header [attr.data-testid]="'crud-editor-action-' + actionName + '-advanced-header'">
|
|
2596
|
+
<mat-panel-title>{{ tx('crud.authoring.action.advanced', 'Submit and API details') }}</mat-panel-title>
|
|
2597
|
+
<mat-panel-description>{{ actionAdvancedSummary(actionName) }}</mat-panel-description>
|
|
2598
|
+
</mat-expansion-panel-header>
|
|
2599
|
+
<section class="action-group action-group--advanced" [attr.data-testid]="'crud-editor-action-' + actionName + '-submit-group'">
|
|
2600
|
+
<div class="action-group__header">
|
|
2601
|
+
<div>
|
|
2602
|
+
<strong>{{ tx('crud.authoring.action.submitGroup', 'Submit contract') }}</strong>
|
|
2603
|
+
<p class="editor-section-note">{{ actionSubmitNote(actionName) }}</p>
|
|
2604
|
+
</div>
|
|
2605
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionGroupStatus(actionName, 'submit') === 'invalid'" [class.action-summary-chip--warn]="actionGroupStatus(actionName, 'submit') === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-submit-status'">
|
|
2606
|
+
{{ actionGroupStatusLabel(actionName, 'submit') }}
|
|
2607
|
+
</span>
|
|
2608
|
+
</div>
|
|
2609
|
+
<div class="editor-grid">
|
|
2610
|
+
<mat-form-field appearance="outline">
|
|
2611
|
+
<mat-label>{{ tx('crud.authoring.action.submitUrl', 'Submit URL') }}</mat-label>
|
|
2612
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-submitUrl'" [ngModel]="actionValue(actionName).form?.submitUrl || ''" (ngModelChange)="setActionFormField(actionName, 'submitUrl', $event)" [disabled]="isReadonly()" />
|
|
2613
|
+
</mat-form-field>
|
|
2614
|
+
<mat-form-field appearance="outline">
|
|
2615
|
+
<mat-label>{{ tx('crud.authoring.action.submitMethod', 'Submit method') }}</mat-label>
|
|
2616
|
+
<mat-select [attr.data-testid]="'crud-editor-action-' + actionName + '-submitMethod'" [ngModel]="actionValue(actionName).form?.submitMethod || ''" (ngModelChange)="setActionFormField(actionName, 'submitMethod', $event)" [disabled]="isReadonly()">
|
|
2617
|
+
<mat-option value="">None</mat-option>
|
|
2618
|
+
@for (method of submitMethods; track method) {
|
|
2619
|
+
<mat-option [value]="method">{{ method.toUpperCase() }}</mat-option>
|
|
2620
|
+
}
|
|
2621
|
+
</mat-select>
|
|
2622
|
+
</mat-form-field>
|
|
2623
|
+
</div>
|
|
2624
|
+
</section>
|
|
2625
|
+
<section class="action-group action-group--advanced" [attr.data-testid]="'crud-editor-action-' + actionName + '-api-group'">
|
|
2626
|
+
<div class="action-group__header">
|
|
2627
|
+
<div>
|
|
2628
|
+
<strong>{{ tx('crud.authoring.action.apiGroup', 'API mapping') }}</strong>
|
|
2629
|
+
<p class="editor-section-note">{{ actionApiNote(actionName) }}</p>
|
|
2630
|
+
</div>
|
|
2631
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionGroupStatus(actionName, 'api') === 'invalid'" [class.action-summary-chip--warn]="actionGroupStatus(actionName, 'api') === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-api-status'">
|
|
2632
|
+
{{ actionGroupStatusLabel(actionName, 'api') }}
|
|
2633
|
+
</span>
|
|
2634
|
+
</div>
|
|
2635
|
+
<div class="editor-grid">
|
|
2636
|
+
<mat-form-field appearance="outline">
|
|
2637
|
+
<mat-label>{{ tx('crud.authoring.action.apiEndpointKey', 'API endpoint key') }}</mat-label>
|
|
2638
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-apiEndpointKey'" [ngModel]="actionValue(actionName).form?.apiEndpointKey || ''" (ngModelChange)="setActionFormField(actionName, 'apiEndpointKey', $event)" [disabled]="isReadonly()" />
|
|
2639
|
+
</mat-form-field>
|
|
2640
|
+
<mat-form-field appearance="outline">
|
|
2641
|
+
<mat-label>{{ tx('crud.authoring.action.apiUrlEntry', 'API URL entry') }}</mat-label>
|
|
2642
|
+
<textarea matInput rows="3" [attr.data-testid]="'crud-editor-action-' + actionName + '-apiUrlEntry'" [ngModel]="actionApiUrlEntryDraft(actionName)" (ngModelChange)="setActionApiUrlEntry(actionName, $event)" [disabled]="isReadonly()"></textarea>
|
|
2643
|
+
</mat-form-field>
|
|
2644
|
+
</div>
|
|
2645
|
+
</section>
|
|
2646
|
+
<section class="action-group action-group--advanced" [attr.data-testid]="'crud-editor-action-' + actionName + '-inputs-group'">
|
|
2647
|
+
<div class="action-group__header">
|
|
2648
|
+
<div>
|
|
2649
|
+
<strong>{{ tx('crud.authoring.action.inputsGroup', 'Input seeding') }}</strong>
|
|
2650
|
+
<p class="editor-section-note">{{ actionInputsNote(actionName) }}</p>
|
|
2651
|
+
</div>
|
|
2652
|
+
</div>
|
|
2653
|
+
<section class="action-subgroup" [attr.data-testid]="'crud-editor-action-' + actionName + '-params-subgroup'">
|
|
2654
|
+
<div class="action-subgroup__header">
|
|
2655
|
+
<strong>{{ tx('crud.authoring.action.paramsSubgroup', 'Row-derived mappings') }}</strong>
|
|
2656
|
+
<p class="editor-section-note">{{ actionParamsNote(actionName) }}</p>
|
|
2657
|
+
</div>
|
|
2658
|
+
<div class="action-param-list">
|
|
2659
|
+
@for (param of actionParams(actionName); track $index) {
|
|
2660
|
+
<div class="action-param-row" [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index">
|
|
2661
|
+
<div class="editor-grid editor-grid--compact">
|
|
2662
|
+
<mat-form-field appearance="outline">
|
|
2663
|
+
<mat-label>{{ tx('crud.authoring.action.paramFrom', 'From field') }}</mat-label>
|
|
2664
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index + '-from'" [ngModel]="param.from" (ngModelChange)="setActionParamField(actionName, $index, 'from', $event)" [disabled]="isReadonly()" />
|
|
2665
|
+
</mat-form-field>
|
|
2666
|
+
<mat-form-field appearance="outline">
|
|
2667
|
+
<mat-label>{{ tx('crud.authoring.action.paramTo', 'Target') }}</mat-label>
|
|
2668
|
+
<mat-select [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index + '-to'" [ngModel]="param.to" (ngModelChange)="setActionParamField(actionName, $index, 'to', $event)" [disabled]="isReadonly()">
|
|
2669
|
+
@for (target of paramTargets; track target) {
|
|
2670
|
+
<mat-option [value]="target">{{ paramTargetLabel(target) }}</mat-option>
|
|
2671
|
+
}
|
|
2672
|
+
</mat-select>
|
|
2673
|
+
</mat-form-field>
|
|
2674
|
+
<mat-form-field appearance="outline">
|
|
2675
|
+
<mat-label>{{ tx('crud.authoring.action.paramName', 'Destination name') }}</mat-label>
|
|
2676
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index + '-name'" [ngModel]="param.name" (ngModelChange)="setActionParamField(actionName, $index, 'name', $event)" [disabled]="isReadonly()" />
|
|
2677
|
+
</mat-form-field>
|
|
2678
|
+
</div>
|
|
2679
|
+
<button mat-stroked-button type="button" [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index + '-remove'" (click)="removeActionParam(actionName, $index)" [disabled]="isReadonly()">
|
|
2680
|
+
{{ tx('crud.authoring.action.paramRemove', 'Remove mapping') }}
|
|
2681
|
+
</button>
|
|
2682
|
+
</div>
|
|
2683
|
+
}
|
|
2684
|
+
<button mat-stroked-button type="button" [attr.data-testid]="'crud-editor-action-' + actionName + '-param-add'" (click)="addActionParam(actionName)" [disabled]="isReadonly()">
|
|
2685
|
+
{{ tx('crud.authoring.action.paramAdd', 'Add mapping') }}
|
|
2686
|
+
</button>
|
|
2687
|
+
</div>
|
|
2688
|
+
</section>
|
|
2689
|
+
<section class="action-subgroup" [attr.data-testid]="'crud-editor-action-' + actionName + '-initialValue-subgroup'">
|
|
2690
|
+
<div class="action-subgroup__header">
|
|
2691
|
+
<strong>{{ tx('crud.authoring.action.initialValueSubgroup', 'Fixed form seed') }}</strong>
|
|
2692
|
+
<p class="editor-section-note">{{ actionInitialValueNote(actionName) }}</p>
|
|
2693
|
+
</div>
|
|
2694
|
+
<mat-form-field appearance="outline">
|
|
2695
|
+
<mat-label>{{ tx('crud.authoring.action.initialValue', 'Initial value seed (JSON)') }}</mat-label>
|
|
2696
|
+
<textarea matInput rows="5" [attr.data-testid]="'crud-editor-action-' + actionName + '-initialValue'" [ngModel]="actionInitialValueDraft(actionName)" (ngModelChange)="setActionInitialValue(actionName, $event)" [disabled]="isReadonly()"></textarea>
|
|
2697
|
+
</mat-form-field>
|
|
2698
|
+
</section>
|
|
2699
|
+
</section>
|
|
2700
|
+
<section class="action-group action-group--advanced" [attr.data-testid]="'crud-editor-action-' + actionName + '-back-group'">
|
|
2701
|
+
<div class="action-group__header">
|
|
2702
|
+
<div>
|
|
2703
|
+
<strong>{{ tx('crud.authoring.action.backGroup', 'Back behavior override') }}</strong>
|
|
2704
|
+
<p class="editor-section-note">{{ actionBackNote(actionName) }}</p>
|
|
2705
|
+
</div>
|
|
2706
|
+
</div>
|
|
2707
|
+
<div class="editor-grid">
|
|
2708
|
+
<mat-form-field appearance="outline">
|
|
2709
|
+
<mat-label>{{ tx('crud.authoring.action.backStrategy', 'Back strategy override') }}</mat-label>
|
|
2710
|
+
<mat-select [attr.data-testid]="'crud-editor-action-' + actionName + '-back-strategy'" [ngModel]="actionBackStrategy(actionName)" (ngModelChange)="setActionBackField(actionName, 'strategy', $event)" [disabled]="isReadonly()">
|
|
2711
|
+
<mat-option value="">{{ tx('crud.authoring.action.backUseDefaults', 'Use defaults') }}</mat-option>
|
|
2712
|
+
@for (strategy of backStrategies; track strategy) {
|
|
2713
|
+
<mat-option [value]="strategy">{{ backStrategyLabel(strategy) }}</mat-option>
|
|
2714
|
+
}
|
|
2715
|
+
</mat-select>
|
|
2716
|
+
</mat-form-field>
|
|
2717
|
+
<mat-form-field appearance="outline">
|
|
2718
|
+
<mat-label>{{ tx('crud.authoring.action.backReturnTo', 'Action return route') }}</mat-label>
|
|
2719
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-back-returnTo'" [ngModel]="actionBackReturnTo(actionName)" (ngModelChange)="setActionBackField(actionName, 'returnTo', $event)" [disabled]="isReadonly()" />
|
|
2720
|
+
</mat-form-field>
|
|
2721
|
+
</div>
|
|
2722
|
+
<div class="editor-toggles">
|
|
2723
|
+
<mat-slide-toggle [attr.data-testid]="'crud-editor-action-' + actionName + '-back-confirmOnDirty'" [ngModel]="actionBackConfirmOnDirty(actionName)" (ngModelChange)="setActionBackField(actionName, 'confirmOnDirty', $event)" [disabled]="isReadonly()">
|
|
2724
|
+
{{ tx('crud.authoring.action.backConfirmOnDirty', 'Confirm when leaving dirty forms') }}
|
|
2725
|
+
</mat-slide-toggle>
|
|
2726
|
+
</div>
|
|
2727
|
+
</section>
|
|
2728
|
+
</mat-expansion-panel>
|
|
2729
|
+
</mat-expansion-panel>
|
|
2730
|
+
}
|
|
2731
|
+
</div>
|
|
2732
|
+
</section>
|
|
2733
|
+
|
|
2734
|
+
<mat-card class="editor-card">
|
|
2735
|
+
<div class="editor-section-header">
|
|
2736
|
+
<div>
|
|
2737
|
+
<h3>{{ tx('crud.authoring.section.table', 'Table') }}</h3>
|
|
2738
|
+
<p class="editor-section-note">{{ sectionSummary('table') }}</p>
|
|
2739
|
+
</div>
|
|
2740
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="sectionStatus('table') === 'invalid'" [class.action-summary-chip--warn]="sectionStatus('table') === 'pending'">
|
|
2741
|
+
{{ sectionStatusLabel('table') }}
|
|
2742
|
+
</span>
|
|
2743
|
+
</div>
|
|
2744
|
+
<section class="action-summary-strip" data-testid="crud-editor-table-summary-strip">
|
|
2745
|
+
<span class="action-summary-chip" data-testid="crud-editor-table-summary-structure">{{ tableStructureSummary() }}</span>
|
|
2746
|
+
<span class="action-summary-chip" data-testid="crud-editor-table-summary-behavior">{{ tableBehaviorSummary() }}</span>
|
|
2747
|
+
<span class="action-summary-chip" data-testid="crud-editor-table-summary-states">{{ tableStatesSummary() }}</span>
|
|
2748
|
+
</section>
|
|
2749
|
+
<p class="editor-section-note" data-testid="crud-editor-table-flow-note">{{ tableEditingFlowSummary() }}</p>
|
|
2750
|
+
<praxis-table-inline-authoring-editor
|
|
2751
|
+
data-testid="crud-editor-table-inline"
|
|
2752
|
+
[config]="tableConfig()"
|
|
2753
|
+
[readonly]="isReadonly()"
|
|
2754
|
+
(configChange)="setTableConfig($event)"
|
|
2755
|
+
/>
|
|
2756
|
+
</mat-card>
|
|
2757
|
+
|
|
2758
|
+
<mat-card class="editor-card">
|
|
2759
|
+
<h3>{{ tx('crud.authoring.section.json', 'JSON') }}</h3>
|
|
2760
|
+
<p class="editor-section-note" data-testid="crud-editor-json-summary">{{ jsonSectionSummary() }}</p>
|
|
2761
|
+
@if (diagnostics().length) {
|
|
2762
|
+
<section class="diagnostics-groups" data-testid="crud-editor-diagnostics">
|
|
2763
|
+
@for (group of groupedDiagnostics(); track group.section) {
|
|
2764
|
+
<article class="diagnostics-group" [attr.data-testid]="'crud-editor-diagnostics-' + group.section">
|
|
2765
|
+
<div class="editor-section-header">
|
|
2766
|
+
<div>
|
|
2767
|
+
<strong>{{ sectionDisplayLabel(group.section) }}</strong>
|
|
2768
|
+
<p class="editor-section-note">{{ diagnosticsGroupSummary(group.section, group.issues) }}</p>
|
|
2769
|
+
</div>
|
|
2770
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="group.hasError" [class.action-summary-chip--warn]="!group.hasError">
|
|
2771
|
+
{{ diagnosticsGroupStatusLabel(group.section, group.issues) }}
|
|
2772
|
+
</span>
|
|
2773
|
+
</div>
|
|
2774
|
+
<ul class="diagnostics">
|
|
2775
|
+
@for (issue of group.issues; track issue.code + issue.path) {
|
|
2776
|
+
<li [class.error]="issue.level === 'error'">{{ issue.level }}: {{ diagnosticMessage(issue.code, issue.message) }}</li>
|
|
2777
|
+
}
|
|
2778
|
+
</ul>
|
|
2779
|
+
</article>
|
|
2780
|
+
}
|
|
2781
|
+
</section>
|
|
2782
|
+
}
|
|
2783
|
+
<pre data-testid="crud-editor-json">{{ serializedDocument() }}</pre>
|
|
2784
|
+
</mat-card>
|
|
2785
|
+
</section>
|
|
2786
|
+
`, isInline: true, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface,#1a1b20)}.editor-shell{display:grid;gap:16px}.editor-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.editor-overview,.editor-health-map{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.editor-health-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-health-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-health-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus{display:flex;gap:12px;align-items:start;padding:14px 16px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-focus--success{background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.editor-focus-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent);white-space:nowrap}.editor-focus-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-focus-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus-copy{display:grid;gap:4px}.editor-focus-copy p{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-overview-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-overview-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-overview-label{font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-value{line-height:1.3;overflow-wrap:anywhere}.editor-overview-note{margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-header h2,.editor-card h3{margin:0}.editor-header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-chip{padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary,#1263b4) 12%,transparent);font-size:12px}.editor-card{display:grid;gap:14px;padding:16px;border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 70%,transparent)}.editor-section-header{display:flex;justify-content:space-between;gap:12px;align-items:start}.editor-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid--compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.editor-toggles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-actions,.action-grid{display:grid;gap:14px}.action-summary-strip{display:flex;flex-wrap:wrap;gap:8px}.action-summary-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.action-summary-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.action-summary-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.action-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 88%,transparent)}.action-group--advanced{background:color-mix(in srgb,var(--md-sys-color-surface-container,#f5f7fb) 90%,transparent)}.action-group__header{display:grid;gap:4px}.action-subgroup{display:grid;gap:10px;padding:10px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low,#fafbfd) 92%,transparent)}.action-subgroup__header{display:grid;gap:4px}.action-param-list{display:grid;gap:12px}.action-param-row{display:grid;gap:8px}.action-advanced-panel{border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 65%,transparent);border-radius:16px}.editor-section-note{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.diagnostics-groups{display:grid;gap:12px}.diagnostics-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.diagnostics{display:grid;gap:8px;margin:0;padding-left:18px}.diagnostics .error{color:var(--md-sys-color-error,#b3261e)}pre{margin:0;overflow:auto;max-height:320px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "readonly"], outputs: ["configChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2787
|
+
}
|
|
2788
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudMetadataEditorComponent, decorators: [{
|
|
2789
|
+
type: Component,
|
|
2790
|
+
args: [{ selector: 'praxis-crud-metadata-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
2791
|
+
CommonModule,
|
|
2792
|
+
FormsModule,
|
|
2793
|
+
MatButtonModule,
|
|
2794
|
+
MatCardModule,
|
|
2795
|
+
MatExpansionModule,
|
|
2796
|
+
MatFormFieldModule,
|
|
2797
|
+
MatInputModule,
|
|
2798
|
+
MatSelectModule,
|
|
2799
|
+
MatSlideToggleModule,
|
|
2800
|
+
PraxisTableInlineAuthoringEditorComponent,
|
|
2801
|
+
], providers: [providePraxisI18nConfig(PRAXIS_CRUD_AUTHORING_I18N_CONFIG)], template: `
|
|
2802
|
+
<section class="editor-shell" data-testid="crud-metadata-editor">
|
|
2803
|
+
<header class="editor-header">
|
|
2804
|
+
<div>
|
|
2805
|
+
<h2>{{ tx('crud.authoring.title', 'CRUD settings') }}</h2>
|
|
2806
|
+
<p>{{ tx('crud.authoring.subtitle', 'Edit the canonical CRUD flow document.') }}</p>
|
|
2807
|
+
</div>
|
|
2808
|
+
<span class="editor-chip" data-testid="crud-metadata-editor-id">{{ effectiveCrudId() || 'lab' }}</span>
|
|
2809
|
+
</header>
|
|
2810
|
+
|
|
2811
|
+
<section class="editor-overview" data-testid="crud-editor-overview">
|
|
2812
|
+
<article class="editor-overview-card">
|
|
2813
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.connection', 'Connection') }}</span>
|
|
2814
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-connection">{{ connectionOverview() }}</strong>
|
|
2815
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-connection-note">{{ connectionOverviewNote() }}</p>
|
|
2816
|
+
</article>
|
|
2817
|
+
<article class="editor-overview-card">
|
|
2818
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.defaults', 'Defaults') }}</span>
|
|
2819
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-defaults">{{ defaultsOverview() }}</strong>
|
|
2820
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-defaults-note">{{ defaultsOverviewNote() }}</p>
|
|
2821
|
+
</article>
|
|
2822
|
+
<article class="editor-overview-card">
|
|
2823
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.actions', 'Actions') }}</span>
|
|
2824
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-actions">{{ actionsOverview() }}</strong>
|
|
2825
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-actions-note">{{ actionsOverviewNote() }}</p>
|
|
2826
|
+
</article>
|
|
2827
|
+
<article class="editor-overview-card">
|
|
2828
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.table', 'Table') }}</span>
|
|
2829
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-table">{{ tableOverview() }}</strong>
|
|
2830
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-table-note">{{ tableOverviewNote() }}</p>
|
|
2831
|
+
</article>
|
|
2832
|
+
<article class="editor-overview-card" [class.editor-overview-card--error]="errorCount() > 0" [class.editor-overview-card--warn]="errorCount() === 0 && warningCount() > 0">
|
|
2833
|
+
<span class="editor-overview-label">{{ tx('crud.authoring.overview.validation', 'Validation') }}</span>
|
|
2834
|
+
<strong class="editor-overview-value" data-testid="crud-editor-overview-validation">{{ validationOverview() }}</strong>
|
|
2835
|
+
<p class="editor-overview-note" data-testid="crud-editor-overview-validation-note">{{ validationOverviewNote() }}</p>
|
|
2836
|
+
</article>
|
|
2837
|
+
</section>
|
|
2838
|
+
|
|
2839
|
+
@if (showHealthMap()) {
|
|
2840
|
+
<section class="editor-health-map" data-testid="crud-editor-health-map">
|
|
2841
|
+
@for (bucket of visibleHealthBuckets(); track bucket) {
|
|
2842
|
+
<article
|
|
2843
|
+
class="editor-health-card"
|
|
2844
|
+
[class.editor-health-card--error]="bucket === 'invalid'"
|
|
2845
|
+
[class.editor-health-card--warn]="bucket === 'pending'"
|
|
2846
|
+
[attr.data-testid]="'crud-editor-health-' + bucket"
|
|
2847
|
+
>
|
|
2848
|
+
<span class="editor-overview-label">{{ healthBucketLabel(bucket) }}</span>
|
|
2849
|
+
<strong>{{ healthBucketSummary(bucket) }}</strong>
|
|
2850
|
+
</article>
|
|
2851
|
+
}
|
|
2852
|
+
</section>
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
<section class="editor-focus" data-testid="crud-editor-next-focus" [class.editor-focus--success]="nextFocusStatus() === 'ready'">
|
|
2856
|
+
<span
|
|
2857
|
+
class="editor-focus-chip"
|
|
2858
|
+
[class.editor-focus-chip--error]="nextFocusStatus() === 'invalid'"
|
|
2859
|
+
[class.editor-focus-chip--warn]="nextFocusStatus() === 'pending'"
|
|
2860
|
+
>
|
|
2861
|
+
{{ nextFocusChipLabel() }}
|
|
2862
|
+
</span>
|
|
2863
|
+
<div class="editor-focus-copy">
|
|
2864
|
+
<strong>{{ nextFocusTitle() }}</strong>
|
|
2865
|
+
<p>{{ nextFocusSummary() }}</p>
|
|
2866
|
+
</div>
|
|
2867
|
+
</section>
|
|
2868
|
+
|
|
2869
|
+
<mat-card class="editor-card">
|
|
2870
|
+
<div class="editor-section-header">
|
|
2871
|
+
<div>
|
|
2872
|
+
<h3>{{ tx('crud.authoring.section.connection', 'Connection') }}</h3>
|
|
2873
|
+
<p class="editor-section-note">{{ sectionSummary('connection') }}</p>
|
|
2874
|
+
</div>
|
|
2875
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="sectionStatus('connection') === 'invalid'" [class.action-summary-chip--warn]="sectionStatus('connection') === 'pending'">
|
|
2876
|
+
{{ sectionStatusLabel('connection') }}
|
|
2877
|
+
</span>
|
|
2878
|
+
</div>
|
|
2879
|
+
<div class="editor-grid">
|
|
2880
|
+
<mat-form-field appearance="outline">
|
|
2881
|
+
<mat-label>{{ tx('crud.authoring.connection.resourcePath', 'Resource') }}</mat-label>
|
|
2882
|
+
<input matInput data-testid="crud-editor-resource-path" [ngModel]="resourcePath()" (ngModelChange)="setResourceField('path', $event)" [disabled]="isReadonly()" />
|
|
2883
|
+
</mat-form-field>
|
|
2884
|
+
<mat-form-field appearance="outline">
|
|
2885
|
+
<mat-label>{{ tx('crud.authoring.connection.idField', 'Identifier field') }}</mat-label>
|
|
2886
|
+
<input matInput data-testid="crud-editor-resource-idField" [ngModel]="resourceIdField()" (ngModelChange)="setResourceField('idField', $event)" [disabled]="isReadonly()" />
|
|
2887
|
+
</mat-form-field>
|
|
2888
|
+
<mat-form-field appearance="outline">
|
|
2889
|
+
<mat-label>{{ tx('crud.authoring.connection.endpointKey', 'Endpoint/API') }}</mat-label>
|
|
2890
|
+
<input matInput data-testid="crud-editor-resource-endpointKey" [ngModel]="resourceEndpointKey()" (ngModelChange)="setResourceField('endpointKey', $event)" [disabled]="isReadonly()" />
|
|
2891
|
+
</mat-form-field>
|
|
2892
|
+
</div>
|
|
2893
|
+
</mat-card>
|
|
2894
|
+
|
|
2895
|
+
<mat-card class="editor-card">
|
|
2896
|
+
<div class="editor-section-header">
|
|
2897
|
+
<div>
|
|
2898
|
+
<h3>{{ tx('crud.authoring.section.defaults', 'Open mode and header') }}</h3>
|
|
2899
|
+
<p class="editor-section-note">{{ sectionSummary('defaults') }}</p>
|
|
2900
|
+
</div>
|
|
2901
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="sectionStatus('defaults') === 'invalid'" [class.action-summary-chip--warn]="sectionStatus('defaults') === 'pending'">
|
|
2902
|
+
{{ sectionStatusLabel('defaults') }}
|
|
2903
|
+
</span>
|
|
2904
|
+
</div>
|
|
2905
|
+
<div class="editor-grid">
|
|
2906
|
+
<mat-form-field appearance="outline">
|
|
2907
|
+
<mat-label>{{ tx('crud.authoring.defaults.openMode', 'Default open mode') }}</mat-label>
|
|
2908
|
+
<mat-select data-testid="crud-editor-defaults-openMode" [ngModel]="defaultsOpenMode()" (ngModelChange)="setDefaultsOpenMode($event)" [disabled]="isReadonly()">
|
|
2909
|
+
@for (mode of openModes; track mode) {
|
|
2910
|
+
<mat-option [value]="mode">{{ modeLabel(mode) }}</mat-option>
|
|
2911
|
+
}
|
|
2912
|
+
</mat-select>
|
|
2913
|
+
</mat-form-field>
|
|
2914
|
+
<mat-form-field appearance="outline">
|
|
2915
|
+
<mat-label>{{ tx('crud.authoring.defaults.header.backLabel', 'Back button label') }}</mat-label>
|
|
2916
|
+
<input matInput data-testid="crud-editor-header-backLabel" [ngModel]="headerBackLabel()" (ngModelChange)="setHeaderField('backLabel', $event)" [disabled]="isReadonly()" />
|
|
2917
|
+
</mat-form-field>
|
|
2918
|
+
<mat-form-field appearance="outline">
|
|
2919
|
+
<mat-label>{{ tx('crud.authoring.defaults.header.variant', 'Button variant') }}</mat-label>
|
|
2920
|
+
<mat-select data-testid="crud-editor-header-variant" [ngModel]="headerVariant()" (ngModelChange)="setHeaderField('variant', $event)" [disabled]="isReadonly()">
|
|
2921
|
+
@for (variant of headerVariants; track variant) {
|
|
2922
|
+
<mat-option [value]="variant">{{ variantLabel(variant) }}</mat-option>
|
|
2923
|
+
}
|
|
2924
|
+
</mat-select>
|
|
2925
|
+
</mat-form-field>
|
|
2926
|
+
</div>
|
|
2927
|
+
<div class="editor-toggles">
|
|
2928
|
+
<mat-slide-toggle data-testid="crud-editor-header-showBack" [ngModel]="headerShowBack()" (ngModelChange)="setHeaderField('showBack', $event)" [disabled]="isReadonly()">
|
|
2929
|
+
{{ tx('crud.authoring.defaults.header.showBack', 'Show back button') }}
|
|
2930
|
+
</mat-slide-toggle>
|
|
2931
|
+
<mat-slide-toggle data-testid="crud-editor-header-sticky" [ngModel]="headerSticky()" (ngModelChange)="setHeaderField('sticky', $event)" [disabled]="isReadonly()">
|
|
2932
|
+
{{ tx('crud.authoring.defaults.header.sticky', 'Sticky header') }}
|
|
2933
|
+
</mat-slide-toggle>
|
|
2934
|
+
<mat-slide-toggle data-testid="crud-editor-header-breadcrumbs" [ngModel]="headerBreadcrumbs()" (ngModelChange)="setHeaderField('breadcrumbs', $event)" [disabled]="isReadonly()">
|
|
2935
|
+
{{ tx('crud.authoring.defaults.header.breadcrumbs', 'Show breadcrumbs') }}
|
|
2936
|
+
</mat-slide-toggle>
|
|
2937
|
+
<mat-slide-toggle data-testid="crud-editor-header-divider" [ngModel]="headerDivider()" (ngModelChange)="setHeaderField('divider', $event)" [disabled]="isReadonly()">
|
|
2938
|
+
{{ tx('crud.authoring.defaults.header.divider', 'Show bottom divider') }}
|
|
2939
|
+
</mat-slide-toggle>
|
|
2940
|
+
</div>
|
|
2941
|
+
<section class="action-group" data-testid="crud-editor-defaults-modal-group">
|
|
2942
|
+
<div class="action-group__header">
|
|
2943
|
+
<div>
|
|
2944
|
+
<strong>{{ tx('crud.authoring.defaults.modal.group', 'Modal presentation') }}</strong>
|
|
2945
|
+
<p class="editor-section-note">{{ modalDefaultsNote() }}</p>
|
|
2946
|
+
</div>
|
|
2947
|
+
</div>
|
|
2948
|
+
<div class="editor-grid">
|
|
2949
|
+
<mat-form-field appearance="outline">
|
|
2950
|
+
<mat-label>{{ tx('crud.authoring.defaults.modal.density', 'Modal density') }}</mat-label>
|
|
2951
|
+
<mat-select data-testid="crud-editor-modal-density" [ngModel]="modalDensity()" (ngModelChange)="setModalField('density', $event)" [disabled]="isReadonly()">
|
|
2952
|
+
@for (density of modalDensities; track density) {
|
|
2953
|
+
<mat-option [value]="density">{{ modalDensityLabel(density) }}</mat-option>
|
|
2954
|
+
}
|
|
2955
|
+
</mat-select>
|
|
2956
|
+
</mat-form-field>
|
|
2957
|
+
<mat-form-field appearance="outline">
|
|
2958
|
+
<mat-label>{{ tx('crud.authoring.defaults.modal.fullscreenBreakpoint', 'Fullscreen breakpoint') }}</mat-label>
|
|
2959
|
+
<input matInput data-testid="crud-editor-modal-fullscreenBreakpoint" [ngModel]="modalFullscreenBreakpoint()" (ngModelChange)="setModalField('fullscreenBreakpoint', $event)" [disabled]="isReadonly()" />
|
|
2960
|
+
</mat-form-field>
|
|
2961
|
+
</div>
|
|
2962
|
+
<div class="editor-toggles">
|
|
2963
|
+
<mat-slide-toggle data-testid="crud-editor-modal-canMaximize" [ngModel]="modalCanMaximize()" (ngModelChange)="setModalField('canMaximize', $event)" [disabled]="isReadonly()">
|
|
2964
|
+
{{ tx('crud.authoring.defaults.modal.canMaximize', 'Show maximize control') }}
|
|
2965
|
+
</mat-slide-toggle>
|
|
2966
|
+
<mat-slide-toggle data-testid="crud-editor-modal-startMaximized" [ngModel]="modalStartMaximized()" (ngModelChange)="setModalField('startMaximized', $event)" [disabled]="isReadonly()">
|
|
2967
|
+
{{ tx('crud.authoring.defaults.modal.startMaximized', 'Start maximized') }}
|
|
2968
|
+
</mat-slide-toggle>
|
|
2969
|
+
<mat-slide-toggle data-testid="crud-editor-modal-rememberLastState" [ngModel]="modalRememberLastState()" (ngModelChange)="setModalField('rememberLastState', $event)" [disabled]="isReadonly()">
|
|
2970
|
+
{{ tx('crud.authoring.defaults.modal.rememberLastState', 'Remember last modal size') }}
|
|
2971
|
+
</mat-slide-toggle>
|
|
2972
|
+
<mat-slide-toggle data-testid="crud-editor-modal-disableCloseOnEsc" [ngModel]="modalDisableCloseOnEsc()" (ngModelChange)="setModalField('disableCloseOnEsc', $event)" [disabled]="isReadonly()">
|
|
2973
|
+
{{ tx('crud.authoring.defaults.modal.disableCloseOnEsc', 'Block close on Escape') }}
|
|
2974
|
+
</mat-slide-toggle>
|
|
2975
|
+
<mat-slide-toggle data-testid="crud-editor-modal-disableCloseOnBackdrop" [ngModel]="modalDisableCloseOnBackdrop()" (ngModelChange)="setModalField('disableCloseOnBackdrop', $event)" [disabled]="isReadonly()">
|
|
2976
|
+
{{ tx('crud.authoring.defaults.modal.disableCloseOnBackdrop', 'Block close on backdrop') }}
|
|
2977
|
+
</mat-slide-toggle>
|
|
2978
|
+
</div>
|
|
2979
|
+
</section>
|
|
2980
|
+
<section class="action-group" data-testid="crud-editor-defaults-back-group">
|
|
2981
|
+
<div class="action-group__header">
|
|
2982
|
+
<div>
|
|
2983
|
+
<strong>{{ tx('crud.authoring.defaults.back.group', 'Back behavior') }}</strong>
|
|
2984
|
+
<p class="editor-section-note">{{ backDefaultsNote() }}</p>
|
|
2985
|
+
</div>
|
|
2986
|
+
</div>
|
|
2987
|
+
<div class="editor-grid">
|
|
2988
|
+
<mat-form-field appearance="outline">
|
|
2989
|
+
<mat-label>{{ tx('crud.authoring.defaults.back.strategy', 'Back strategy') }}</mat-label>
|
|
2990
|
+
<mat-select data-testid="crud-editor-back-strategy" [ngModel]="backStrategy()" (ngModelChange)="setBackField('strategy', $event)" [disabled]="isReadonly()">
|
|
2991
|
+
@for (strategy of backStrategies; track strategy) {
|
|
2992
|
+
<mat-option [value]="strategy">{{ backStrategyLabel(strategy) }}</mat-option>
|
|
2993
|
+
}
|
|
2994
|
+
</mat-select>
|
|
2995
|
+
</mat-form-field>
|
|
2996
|
+
<mat-form-field appearance="outline">
|
|
2997
|
+
<mat-label>{{ tx('crud.authoring.defaults.back.returnTo', 'Return route') }}</mat-label>
|
|
2998
|
+
<input matInput data-testid="crud-editor-back-returnTo" [ngModel]="backReturnTo()" (ngModelChange)="setBackField('returnTo', $event)" [disabled]="isReadonly()" />
|
|
2999
|
+
</mat-form-field>
|
|
3000
|
+
</div>
|
|
3001
|
+
<div class="editor-toggles">
|
|
3002
|
+
<mat-slide-toggle data-testid="crud-editor-back-confirmOnDirty" [ngModel]="backConfirmOnDirty()" (ngModelChange)="setBackField('confirmOnDirty', $event)" [disabled]="isReadonly()">
|
|
3003
|
+
{{ tx('crud.authoring.defaults.back.confirmOnDirty', 'Confirm when leaving dirty forms') }}
|
|
3004
|
+
</mat-slide-toggle>
|
|
3005
|
+
</div>
|
|
3006
|
+
</section>
|
|
3007
|
+
</mat-card>
|
|
3008
|
+
|
|
3009
|
+
<section class="editor-actions">
|
|
3010
|
+
<div class="editor-section-header">
|
|
3011
|
+
<div>
|
|
3012
|
+
<h3>{{ tx('crud.authoring.section.actions', 'Actions') }}</h3>
|
|
3013
|
+
<p class="editor-section-note">{{ sectionSummary('actions') }}</p>
|
|
3014
|
+
</div>
|
|
3015
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="sectionStatus('actions') === 'invalid'" [class.action-summary-chip--warn]="sectionStatus('actions') === 'pending'">
|
|
3016
|
+
{{ sectionStatusLabel('actions') }}
|
|
3017
|
+
</span>
|
|
3018
|
+
</div>
|
|
3019
|
+
<div class="action-grid">
|
|
3020
|
+
@for (actionName of actionKeys; track actionName) {
|
|
3021
|
+
<mat-expansion-panel [expanded]="actionPanelExpanded(actionName)" class="editor-card" [attr.data-testid]="'crud-editor-action-' + actionName + '-panel'">
|
|
3022
|
+
<mat-expansion-panel-header [attr.data-testid]="'crud-editor-action-' + actionName + '-panel-header'">
|
|
3023
|
+
<mat-panel-title>{{ actionLabel(actionName) }}</mat-panel-title>
|
|
3024
|
+
<mat-panel-description [attr.data-testid]="'crud-editor-action-' + actionName + '-header-summary'">{{ actionSummary(actionName) }}</mat-panel-description>
|
|
3025
|
+
</mat-expansion-panel-header>
|
|
3026
|
+
<section class="action-summary-strip" [attr.data-testid]="'crud-editor-action-' + actionName + '-summary'">
|
|
3027
|
+
@if (actionShowStatusChip(actionName)) {
|
|
3028
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionStatus(actionName) === 'invalid'" [class.action-summary-chip--warn]="actionStatus(actionName) === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-status-chip'">
|
|
3029
|
+
{{ actionStatusLabel(actionName) }}
|
|
3030
|
+
</span>
|
|
3031
|
+
}
|
|
3032
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionStatus(actionName) === 'invalid'" [class.action-summary-chip--warn]="actionStatus(actionName) !== 'ready'" [attr.data-testid]="'crud-editor-action-' + actionName + '-primary-summary'">
|
|
3033
|
+
{{ actionPrimarySummary(actionName) }}
|
|
3034
|
+
</span>
|
|
3035
|
+
<span class="action-summary-chip" [attr.data-testid]="'crud-editor-action-' + actionName + '-advanced-summary'">{{ actionAdvancedSummary(actionName) }}</span>
|
|
3036
|
+
</section>
|
|
3037
|
+
<section class="action-group" [attr.data-testid]="'crud-editor-action-' + actionName + '-binding-group'">
|
|
3038
|
+
<div class="action-group__header">
|
|
3039
|
+
<div>
|
|
3040
|
+
<strong>{{ tx('crud.authoring.action.bindingGroup', 'Binding') }}</strong>
|
|
3041
|
+
<p class="editor-section-note">{{ actionBindingNote(actionName) }}</p>
|
|
3042
|
+
</div>
|
|
3043
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionGroupStatus(actionName, 'binding') === 'invalid'" [class.action-summary-chip--warn]="actionGroupStatus(actionName, 'binding') === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-binding-status'">
|
|
3044
|
+
{{ actionGroupStatusLabel(actionName, 'binding') }}
|
|
3045
|
+
</span>
|
|
3046
|
+
</div>
|
|
3047
|
+
<div class="editor-grid">
|
|
3048
|
+
<mat-form-field appearance="outline">
|
|
3049
|
+
<mat-label>{{ tx('crud.authoring.action.mode', 'Open mode') }}</mat-label>
|
|
3050
|
+
<mat-select [attr.data-testid]="'crud-editor-action-' + actionName + '-openMode'" [ngModel]="actionValue(actionName).openMode || ''" (ngModelChange)="setActionField(actionName, 'openMode', $event)" [disabled]="isReadonly()">
|
|
3051
|
+
<mat-option value="">Default</mat-option>
|
|
3052
|
+
@for (mode of openModes; track mode) {
|
|
3053
|
+
<mat-option [value]="mode">{{ modeLabel(mode) }}</mat-option>
|
|
3054
|
+
}
|
|
3055
|
+
</mat-select>
|
|
3056
|
+
</mat-form-field>
|
|
3057
|
+
<mat-form-field appearance="outline">
|
|
3058
|
+
<mat-label>{{ tx('crud.authoring.action.route', 'Route') }}</mat-label>
|
|
3059
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-route'" [ngModel]="actionValue(actionName).route || ''" (ngModelChange)="setActionField(actionName, 'route', $event)" [disabled]="isReadonly()" />
|
|
3060
|
+
</mat-form-field>
|
|
3061
|
+
<mat-form-field appearance="outline">
|
|
3062
|
+
<mat-label>{{ tx('crud.authoring.action.formId', 'formId') }}</mat-label>
|
|
3063
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-formId'" [ngModel]="actionValue(actionName).formId || ''" (ngModelChange)="setActionField(actionName, 'formId', $event)" [disabled]="isReadonly()" />
|
|
3064
|
+
</mat-form-field>
|
|
3065
|
+
</div>
|
|
3066
|
+
</section>
|
|
3067
|
+
<section class="action-group" [attr.data-testid]="'crud-editor-action-' + actionName + '-schema-group'">
|
|
3068
|
+
<div class="action-group__header">
|
|
3069
|
+
<div>
|
|
3070
|
+
<strong>{{ tx('crud.authoring.action.schemaGroup', 'Schema contract') }}</strong>
|
|
3071
|
+
<p class="editor-section-note">{{ actionSchemaNote(actionName) }}</p>
|
|
3072
|
+
</div>
|
|
3073
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionGroupStatus(actionName, 'schema') === 'invalid'" [class.action-summary-chip--warn]="actionGroupStatus(actionName, 'schema') === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-schema-status'">
|
|
3074
|
+
{{ actionGroupStatusLabel(actionName, 'schema') }}
|
|
3075
|
+
</span>
|
|
3076
|
+
</div>
|
|
3077
|
+
<div class="editor-grid">
|
|
3078
|
+
<mat-form-field appearance="outline">
|
|
3079
|
+
<mat-label>{{ tx('crud.authoring.action.schemaUrl', 'Schema URL') }}</mat-label>
|
|
3080
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-schemaUrl'" [ngModel]="actionValue(actionName).form?.schemaUrl || ''" (ngModelChange)="setActionFormField(actionName, 'schemaUrl', $event)" [disabled]="isReadonly()" />
|
|
3081
|
+
</mat-form-field>
|
|
3082
|
+
</div>
|
|
3083
|
+
</section>
|
|
3084
|
+
<mat-expansion-panel class="action-advanced-panel" [expanded]="actionAdvancedPanelExpanded(actionName)" [attr.data-testid]="'crud-editor-action-' + actionName + '-advanced-panel'">
|
|
3085
|
+
<mat-expansion-panel-header [attr.data-testid]="'crud-editor-action-' + actionName + '-advanced-header'">
|
|
3086
|
+
<mat-panel-title>{{ tx('crud.authoring.action.advanced', 'Submit and API details') }}</mat-panel-title>
|
|
3087
|
+
<mat-panel-description>{{ actionAdvancedSummary(actionName) }}</mat-panel-description>
|
|
3088
|
+
</mat-expansion-panel-header>
|
|
3089
|
+
<section class="action-group action-group--advanced" [attr.data-testid]="'crud-editor-action-' + actionName + '-submit-group'">
|
|
3090
|
+
<div class="action-group__header">
|
|
3091
|
+
<div>
|
|
3092
|
+
<strong>{{ tx('crud.authoring.action.submitGroup', 'Submit contract') }}</strong>
|
|
3093
|
+
<p class="editor-section-note">{{ actionSubmitNote(actionName) }}</p>
|
|
3094
|
+
</div>
|
|
3095
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionGroupStatus(actionName, 'submit') === 'invalid'" [class.action-summary-chip--warn]="actionGroupStatus(actionName, 'submit') === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-submit-status'">
|
|
3096
|
+
{{ actionGroupStatusLabel(actionName, 'submit') }}
|
|
3097
|
+
</span>
|
|
3098
|
+
</div>
|
|
3099
|
+
<div class="editor-grid">
|
|
3100
|
+
<mat-form-field appearance="outline">
|
|
3101
|
+
<mat-label>{{ tx('crud.authoring.action.submitUrl', 'Submit URL') }}</mat-label>
|
|
3102
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-submitUrl'" [ngModel]="actionValue(actionName).form?.submitUrl || ''" (ngModelChange)="setActionFormField(actionName, 'submitUrl', $event)" [disabled]="isReadonly()" />
|
|
3103
|
+
</mat-form-field>
|
|
3104
|
+
<mat-form-field appearance="outline">
|
|
3105
|
+
<mat-label>{{ tx('crud.authoring.action.submitMethod', 'Submit method') }}</mat-label>
|
|
3106
|
+
<mat-select [attr.data-testid]="'crud-editor-action-' + actionName + '-submitMethod'" [ngModel]="actionValue(actionName).form?.submitMethod || ''" (ngModelChange)="setActionFormField(actionName, 'submitMethod', $event)" [disabled]="isReadonly()">
|
|
3107
|
+
<mat-option value="">None</mat-option>
|
|
3108
|
+
@for (method of submitMethods; track method) {
|
|
3109
|
+
<mat-option [value]="method">{{ method.toUpperCase() }}</mat-option>
|
|
3110
|
+
}
|
|
3111
|
+
</mat-select>
|
|
3112
|
+
</mat-form-field>
|
|
3113
|
+
</div>
|
|
3114
|
+
</section>
|
|
3115
|
+
<section class="action-group action-group--advanced" [attr.data-testid]="'crud-editor-action-' + actionName + '-api-group'">
|
|
3116
|
+
<div class="action-group__header">
|
|
3117
|
+
<div>
|
|
3118
|
+
<strong>{{ tx('crud.authoring.action.apiGroup', 'API mapping') }}</strong>
|
|
3119
|
+
<p class="editor-section-note">{{ actionApiNote(actionName) }}</p>
|
|
3120
|
+
</div>
|
|
3121
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="actionGroupStatus(actionName, 'api') === 'invalid'" [class.action-summary-chip--warn]="actionGroupStatus(actionName, 'api') === 'pending'" [attr.data-testid]="'crud-editor-action-' + actionName + '-api-status'">
|
|
3122
|
+
{{ actionGroupStatusLabel(actionName, 'api') }}
|
|
3123
|
+
</span>
|
|
3124
|
+
</div>
|
|
3125
|
+
<div class="editor-grid">
|
|
3126
|
+
<mat-form-field appearance="outline">
|
|
3127
|
+
<mat-label>{{ tx('crud.authoring.action.apiEndpointKey', 'API endpoint key') }}</mat-label>
|
|
3128
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-apiEndpointKey'" [ngModel]="actionValue(actionName).form?.apiEndpointKey || ''" (ngModelChange)="setActionFormField(actionName, 'apiEndpointKey', $event)" [disabled]="isReadonly()" />
|
|
3129
|
+
</mat-form-field>
|
|
3130
|
+
<mat-form-field appearance="outline">
|
|
3131
|
+
<mat-label>{{ tx('crud.authoring.action.apiUrlEntry', 'API URL entry') }}</mat-label>
|
|
3132
|
+
<textarea matInput rows="3" [attr.data-testid]="'crud-editor-action-' + actionName + '-apiUrlEntry'" [ngModel]="actionApiUrlEntryDraft(actionName)" (ngModelChange)="setActionApiUrlEntry(actionName, $event)" [disabled]="isReadonly()"></textarea>
|
|
3133
|
+
</mat-form-field>
|
|
3134
|
+
</div>
|
|
3135
|
+
</section>
|
|
3136
|
+
<section class="action-group action-group--advanced" [attr.data-testid]="'crud-editor-action-' + actionName + '-inputs-group'">
|
|
3137
|
+
<div class="action-group__header">
|
|
3138
|
+
<div>
|
|
3139
|
+
<strong>{{ tx('crud.authoring.action.inputsGroup', 'Input seeding') }}</strong>
|
|
3140
|
+
<p class="editor-section-note">{{ actionInputsNote(actionName) }}</p>
|
|
3141
|
+
</div>
|
|
3142
|
+
</div>
|
|
3143
|
+
<section class="action-subgroup" [attr.data-testid]="'crud-editor-action-' + actionName + '-params-subgroup'">
|
|
3144
|
+
<div class="action-subgroup__header">
|
|
3145
|
+
<strong>{{ tx('crud.authoring.action.paramsSubgroup', 'Row-derived mappings') }}</strong>
|
|
3146
|
+
<p class="editor-section-note">{{ actionParamsNote(actionName) }}</p>
|
|
3147
|
+
</div>
|
|
3148
|
+
<div class="action-param-list">
|
|
3149
|
+
@for (param of actionParams(actionName); track $index) {
|
|
3150
|
+
<div class="action-param-row" [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index">
|
|
3151
|
+
<div class="editor-grid editor-grid--compact">
|
|
3152
|
+
<mat-form-field appearance="outline">
|
|
3153
|
+
<mat-label>{{ tx('crud.authoring.action.paramFrom', 'From field') }}</mat-label>
|
|
3154
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index + '-from'" [ngModel]="param.from" (ngModelChange)="setActionParamField(actionName, $index, 'from', $event)" [disabled]="isReadonly()" />
|
|
3155
|
+
</mat-form-field>
|
|
3156
|
+
<mat-form-field appearance="outline">
|
|
3157
|
+
<mat-label>{{ tx('crud.authoring.action.paramTo', 'Target') }}</mat-label>
|
|
3158
|
+
<mat-select [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index + '-to'" [ngModel]="param.to" (ngModelChange)="setActionParamField(actionName, $index, 'to', $event)" [disabled]="isReadonly()">
|
|
3159
|
+
@for (target of paramTargets; track target) {
|
|
3160
|
+
<mat-option [value]="target">{{ paramTargetLabel(target) }}</mat-option>
|
|
3161
|
+
}
|
|
3162
|
+
</mat-select>
|
|
3163
|
+
</mat-form-field>
|
|
3164
|
+
<mat-form-field appearance="outline">
|
|
3165
|
+
<mat-label>{{ tx('crud.authoring.action.paramName', 'Destination name') }}</mat-label>
|
|
3166
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index + '-name'" [ngModel]="param.name" (ngModelChange)="setActionParamField(actionName, $index, 'name', $event)" [disabled]="isReadonly()" />
|
|
3167
|
+
</mat-form-field>
|
|
3168
|
+
</div>
|
|
3169
|
+
<button mat-stroked-button type="button" [attr.data-testid]="'crud-editor-action-' + actionName + '-param-' + $index + '-remove'" (click)="removeActionParam(actionName, $index)" [disabled]="isReadonly()">
|
|
3170
|
+
{{ tx('crud.authoring.action.paramRemove', 'Remove mapping') }}
|
|
3171
|
+
</button>
|
|
3172
|
+
</div>
|
|
3173
|
+
}
|
|
3174
|
+
<button mat-stroked-button type="button" [attr.data-testid]="'crud-editor-action-' + actionName + '-param-add'" (click)="addActionParam(actionName)" [disabled]="isReadonly()">
|
|
3175
|
+
{{ tx('crud.authoring.action.paramAdd', 'Add mapping') }}
|
|
3176
|
+
</button>
|
|
3177
|
+
</div>
|
|
3178
|
+
</section>
|
|
3179
|
+
<section class="action-subgroup" [attr.data-testid]="'crud-editor-action-' + actionName + '-initialValue-subgroup'">
|
|
3180
|
+
<div class="action-subgroup__header">
|
|
3181
|
+
<strong>{{ tx('crud.authoring.action.initialValueSubgroup', 'Fixed form seed') }}</strong>
|
|
3182
|
+
<p class="editor-section-note">{{ actionInitialValueNote(actionName) }}</p>
|
|
3183
|
+
</div>
|
|
3184
|
+
<mat-form-field appearance="outline">
|
|
3185
|
+
<mat-label>{{ tx('crud.authoring.action.initialValue', 'Initial value seed (JSON)') }}</mat-label>
|
|
3186
|
+
<textarea matInput rows="5" [attr.data-testid]="'crud-editor-action-' + actionName + '-initialValue'" [ngModel]="actionInitialValueDraft(actionName)" (ngModelChange)="setActionInitialValue(actionName, $event)" [disabled]="isReadonly()"></textarea>
|
|
3187
|
+
</mat-form-field>
|
|
3188
|
+
</section>
|
|
3189
|
+
</section>
|
|
3190
|
+
<section class="action-group action-group--advanced" [attr.data-testid]="'crud-editor-action-' + actionName + '-back-group'">
|
|
3191
|
+
<div class="action-group__header">
|
|
3192
|
+
<div>
|
|
3193
|
+
<strong>{{ tx('crud.authoring.action.backGroup', 'Back behavior override') }}</strong>
|
|
3194
|
+
<p class="editor-section-note">{{ actionBackNote(actionName) }}</p>
|
|
3195
|
+
</div>
|
|
3196
|
+
</div>
|
|
3197
|
+
<div class="editor-grid">
|
|
3198
|
+
<mat-form-field appearance="outline">
|
|
3199
|
+
<mat-label>{{ tx('crud.authoring.action.backStrategy', 'Back strategy override') }}</mat-label>
|
|
3200
|
+
<mat-select [attr.data-testid]="'crud-editor-action-' + actionName + '-back-strategy'" [ngModel]="actionBackStrategy(actionName)" (ngModelChange)="setActionBackField(actionName, 'strategy', $event)" [disabled]="isReadonly()">
|
|
3201
|
+
<mat-option value="">{{ tx('crud.authoring.action.backUseDefaults', 'Use defaults') }}</mat-option>
|
|
3202
|
+
@for (strategy of backStrategies; track strategy) {
|
|
3203
|
+
<mat-option [value]="strategy">{{ backStrategyLabel(strategy) }}</mat-option>
|
|
3204
|
+
}
|
|
3205
|
+
</mat-select>
|
|
3206
|
+
</mat-form-field>
|
|
3207
|
+
<mat-form-field appearance="outline">
|
|
3208
|
+
<mat-label>{{ tx('crud.authoring.action.backReturnTo', 'Action return route') }}</mat-label>
|
|
3209
|
+
<input matInput [attr.data-testid]="'crud-editor-action-' + actionName + '-back-returnTo'" [ngModel]="actionBackReturnTo(actionName)" (ngModelChange)="setActionBackField(actionName, 'returnTo', $event)" [disabled]="isReadonly()" />
|
|
3210
|
+
</mat-form-field>
|
|
3211
|
+
</div>
|
|
3212
|
+
<div class="editor-toggles">
|
|
3213
|
+
<mat-slide-toggle [attr.data-testid]="'crud-editor-action-' + actionName + '-back-confirmOnDirty'" [ngModel]="actionBackConfirmOnDirty(actionName)" (ngModelChange)="setActionBackField(actionName, 'confirmOnDirty', $event)" [disabled]="isReadonly()">
|
|
3214
|
+
{{ tx('crud.authoring.action.backConfirmOnDirty', 'Confirm when leaving dirty forms') }}
|
|
3215
|
+
</mat-slide-toggle>
|
|
3216
|
+
</div>
|
|
3217
|
+
</section>
|
|
3218
|
+
</mat-expansion-panel>
|
|
3219
|
+
</mat-expansion-panel>
|
|
3220
|
+
}
|
|
3221
|
+
</div>
|
|
3222
|
+
</section>
|
|
3223
|
+
|
|
3224
|
+
<mat-card class="editor-card">
|
|
3225
|
+
<div class="editor-section-header">
|
|
3226
|
+
<div>
|
|
3227
|
+
<h3>{{ tx('crud.authoring.section.table', 'Table') }}</h3>
|
|
3228
|
+
<p class="editor-section-note">{{ sectionSummary('table') }}</p>
|
|
3229
|
+
</div>
|
|
3230
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="sectionStatus('table') === 'invalid'" [class.action-summary-chip--warn]="sectionStatus('table') === 'pending'">
|
|
3231
|
+
{{ sectionStatusLabel('table') }}
|
|
3232
|
+
</span>
|
|
3233
|
+
</div>
|
|
3234
|
+
<section class="action-summary-strip" data-testid="crud-editor-table-summary-strip">
|
|
3235
|
+
<span class="action-summary-chip" data-testid="crud-editor-table-summary-structure">{{ tableStructureSummary() }}</span>
|
|
3236
|
+
<span class="action-summary-chip" data-testid="crud-editor-table-summary-behavior">{{ tableBehaviorSummary() }}</span>
|
|
3237
|
+
<span class="action-summary-chip" data-testid="crud-editor-table-summary-states">{{ tableStatesSummary() }}</span>
|
|
3238
|
+
</section>
|
|
3239
|
+
<p class="editor-section-note" data-testid="crud-editor-table-flow-note">{{ tableEditingFlowSummary() }}</p>
|
|
3240
|
+
<praxis-table-inline-authoring-editor
|
|
3241
|
+
data-testid="crud-editor-table-inline"
|
|
3242
|
+
[config]="tableConfig()"
|
|
3243
|
+
[readonly]="isReadonly()"
|
|
3244
|
+
(configChange)="setTableConfig($event)"
|
|
3245
|
+
/>
|
|
3246
|
+
</mat-card>
|
|
3247
|
+
|
|
3248
|
+
<mat-card class="editor-card">
|
|
3249
|
+
<h3>{{ tx('crud.authoring.section.json', 'JSON') }}</h3>
|
|
3250
|
+
<p class="editor-section-note" data-testid="crud-editor-json-summary">{{ jsonSectionSummary() }}</p>
|
|
3251
|
+
@if (diagnostics().length) {
|
|
3252
|
+
<section class="diagnostics-groups" data-testid="crud-editor-diagnostics">
|
|
3253
|
+
@for (group of groupedDiagnostics(); track group.section) {
|
|
3254
|
+
<article class="diagnostics-group" [attr.data-testid]="'crud-editor-diagnostics-' + group.section">
|
|
3255
|
+
<div class="editor-section-header">
|
|
3256
|
+
<div>
|
|
3257
|
+
<strong>{{ sectionDisplayLabel(group.section) }}</strong>
|
|
3258
|
+
<p class="editor-section-note">{{ diagnosticsGroupSummary(group.section, group.issues) }}</p>
|
|
3259
|
+
</div>
|
|
3260
|
+
<span class="action-summary-chip" [class.action-summary-chip--error]="group.hasError" [class.action-summary-chip--warn]="!group.hasError">
|
|
3261
|
+
{{ diagnosticsGroupStatusLabel(group.section, group.issues) }}
|
|
3262
|
+
</span>
|
|
3263
|
+
</div>
|
|
3264
|
+
<ul class="diagnostics">
|
|
3265
|
+
@for (issue of group.issues; track issue.code + issue.path) {
|
|
3266
|
+
<li [class.error]="issue.level === 'error'">{{ issue.level }}: {{ diagnosticMessage(issue.code, issue.message) }}</li>
|
|
3267
|
+
}
|
|
3268
|
+
</ul>
|
|
3269
|
+
</article>
|
|
3270
|
+
}
|
|
3271
|
+
</section>
|
|
3272
|
+
}
|
|
3273
|
+
<pre data-testid="crud-editor-json">{{ serializedDocument() }}</pre>
|
|
3274
|
+
</mat-card>
|
|
3275
|
+
</section>
|
|
3276
|
+
`, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface,#1a1b20)}.editor-shell{display:grid;gap:16px}.editor-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.editor-overview,.editor-health-map{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.editor-health-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-health-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-health-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus{display:flex;gap:12px;align-items:start;padding:14px 16px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-focus--success{background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.editor-focus-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent);white-space:nowrap}.editor-focus-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-focus-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus-copy{display:grid;gap:4px}.editor-focus-copy p{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-overview-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-overview-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-overview-label{font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-value{line-height:1.3;overflow-wrap:anywhere}.editor-overview-note{margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-header h2,.editor-card h3{margin:0}.editor-header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-chip{padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary,#1263b4) 12%,transparent);font-size:12px}.editor-card{display:grid;gap:14px;padding:16px;border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 70%,transparent)}.editor-section-header{display:flex;justify-content:space-between;gap:12px;align-items:start}.editor-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid--compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.editor-toggles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-actions,.action-grid{display:grid;gap:14px}.action-summary-strip{display:flex;flex-wrap:wrap;gap:8px}.action-summary-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.action-summary-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.action-summary-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.action-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 88%,transparent)}.action-group--advanced{background:color-mix(in srgb,var(--md-sys-color-surface-container,#f5f7fb) 90%,transparent)}.action-group__header{display:grid;gap:4px}.action-subgroup{display:grid;gap:10px;padding:10px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low,#fafbfd) 92%,transparent)}.action-subgroup__header{display:grid;gap:4px}.action-param-list{display:grid;gap:12px}.action-param-row{display:grid;gap:8px}.action-advanced-panel{border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 65%,transparent);border-radius:16px}.editor-section-note{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.diagnostics-groups{display:grid;gap:12px}.diagnostics-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.diagnostics{display:grid;gap:8px;margin:0;padding-left:18px}.diagnostics .error{color:var(--md-sys-color-error,#b3261e)}pre{margin:0;overflow:auto;max-height:320px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}\n"] }]
|
|
3277
|
+
}], ctorParameters: () => [], propDecorators: { documentInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: false }] }], metadataInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "metadata", required: false }] }], crudIdInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "crudId", required: false }] }], readonlyInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }] } });
|
|
3278
|
+
function cloneTableConfig(config) {
|
|
3279
|
+
return JSON.parse(JSON.stringify(config || { columns: [] }));
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
function openCrudMetadataEditor(settings, opts) {
|
|
3283
|
+
return settings.open({
|
|
3284
|
+
id: opts?.id ?? `crud-authoring:${opts?.crudId || 'default'}`,
|
|
3285
|
+
title: opts?.title ?? 'CRUD settings',
|
|
3286
|
+
titleIcon: opts?.titleIcon ?? 'tune',
|
|
3287
|
+
content: {
|
|
3288
|
+
component: CrudMetadataEditorComponent,
|
|
3289
|
+
inputs: {
|
|
3290
|
+
document: opts?.document ?? null,
|
|
3291
|
+
metadata: opts?.metadata ?? null,
|
|
3292
|
+
crudId: opts?.crudId ?? null,
|
|
3293
|
+
readonly: opts?.readonly ?? false,
|
|
3294
|
+
},
|
|
3295
|
+
},
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
|
|
375
3299
|
class PraxisCrudComponent {
|
|
376
3300
|
metadata;
|
|
377
3301
|
crudId;
|
|
@@ -385,6 +3309,8 @@ class PraxisCrudComponent {
|
|
|
385
3309
|
afterDelete = new EventEmitter();
|
|
386
3310
|
error = new EventEmitter();
|
|
387
3311
|
tableRuntimeConfigChange = new EventEmitter();
|
|
3312
|
+
crudAuthoringDocumentApplied = new EventEmitter();
|
|
3313
|
+
crudAuthoringDocumentSaved = new EventEmitter();
|
|
388
3314
|
resolvedMetadata;
|
|
389
3315
|
effectiveTableConfig;
|
|
390
3316
|
tableConfigForBinding = createDefaultTableConfig();
|
|
@@ -392,11 +3318,14 @@ class PraxisCrudComponent {
|
|
|
392
3318
|
tableFilterCriteria = {};
|
|
393
3319
|
tableCrudContext;
|
|
394
3320
|
launcher = inject(CrudLauncherService);
|
|
3321
|
+
http = inject(HttpClient);
|
|
395
3322
|
destroyRef = inject(DestroyRef);
|
|
396
3323
|
cdr = inject(ChangeDetectorRef);
|
|
397
3324
|
table;
|
|
398
3325
|
storage = inject(ASYNC_CONFIG_STORAGE);
|
|
3326
|
+
settingsPanel = inject(SettingsPanelService);
|
|
399
3327
|
snack = inject(MatSnackBar);
|
|
3328
|
+
dialog = inject(DialogService);
|
|
400
3329
|
i18n = inject(PraxisI18nService);
|
|
401
3330
|
resourceDiscovery = inject(ResourceDiscoveryService);
|
|
402
3331
|
actionOpenAdapter = inject(ResourceActionOpenAdapterService);
|
|
@@ -429,6 +3358,7 @@ class PraxisCrudComponent {
|
|
|
429
3358
|
collectionCapabilitiesRequestHref = null;
|
|
430
3359
|
collectionCapabilitiesResolvedHref = null;
|
|
431
3360
|
collectionCapabilitiesRequestSeq = 0;
|
|
3361
|
+
currentAuthoringDocument;
|
|
432
3362
|
onResetPreferences() {
|
|
433
3363
|
try {
|
|
434
3364
|
const keyId = this.componentKeyId();
|
|
@@ -443,7 +3373,7 @@ class PraxisCrudComponent {
|
|
|
443
3373
|
catch { }
|
|
444
3374
|
}
|
|
445
3375
|
ngOnChanges(changes) {
|
|
446
|
-
if (!changes['metadata']) {
|
|
3376
|
+
if (!changes['metadata'] && !changes['context']) {
|
|
447
3377
|
return;
|
|
448
3378
|
}
|
|
449
3379
|
try {
|
|
@@ -451,6 +3381,15 @@ class PraxisCrudComponent {
|
|
|
451
3381
|
? JSON.parse(this.metadata)
|
|
452
3382
|
: this.metadata;
|
|
453
3383
|
this.resolvedMetadata = parsed;
|
|
3384
|
+
const externalAuthoringSeed = this.context && typeof this.context === 'object'
|
|
3385
|
+
? (this.context['authoringDocument'] ?? this.context['authoringMetadata'])
|
|
3386
|
+
: undefined;
|
|
3387
|
+
this.currentAuthoringDocument = externalAuthoringSeed
|
|
3388
|
+
? parseLegacyOrCrudDocument(externalAuthoringSeed)
|
|
3389
|
+
: createCrudAuthoringDocument({
|
|
3390
|
+
metadata: this.resolvedMetadata,
|
|
3391
|
+
});
|
|
3392
|
+
this.resolvedMetadata = this.currentAuthoringDocument.metadata;
|
|
454
3393
|
assertCrudMetadata(this.resolvedMetadata, {
|
|
455
3394
|
allowDeferredActionBindings: true,
|
|
456
3395
|
});
|
|
@@ -494,6 +3433,10 @@ class PraxisCrudComponent {
|
|
|
494
3433
|
}
|
|
495
3434
|
}
|
|
496
3435
|
const effectiveAction = (actionMeta || { action });
|
|
3436
|
+
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, row);
|
|
3437
|
+
if (handledByDelete) {
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
497
3440
|
let drawerCloseEmitted = false;
|
|
498
3441
|
const emitDrawerClose = () => {
|
|
499
3442
|
if (drawerCloseEmitted)
|
|
@@ -518,6 +3461,11 @@ class PraxisCrudComponent {
|
|
|
518
3461
|
this.refreshTable();
|
|
519
3462
|
}
|
|
520
3463
|
},
|
|
3464
|
+
}, {
|
|
3465
|
+
capabilities: effectiveAction.action === 'create'
|
|
3466
|
+
? this.collectionCapabilities
|
|
3467
|
+
: this.collectionCapabilities,
|
|
3468
|
+
links: this.resolveCrudRuntimeLinks(effectiveAction.action, row),
|
|
521
3469
|
});
|
|
522
3470
|
this.afterOpen.emit({ mode, action: effectiveAction.action });
|
|
523
3471
|
if (!ref) {
|
|
@@ -644,6 +3592,53 @@ class PraxisCrudComponent {
|
|
|
644
3592
|
onConfigureRequested() {
|
|
645
3593
|
this.configureRequested.emit();
|
|
646
3594
|
}
|
|
3595
|
+
async tryHandleCanonicalDeleteAction(action, row) {
|
|
3596
|
+
const normalizedAction = String(action.action || '').trim().toLowerCase();
|
|
3597
|
+
if (normalizedAction !== 'delete') {
|
|
3598
|
+
return false;
|
|
3599
|
+
}
|
|
3600
|
+
const effectiveOpenMode = action.openMode ?? this.resolvedMetadata.defaults?.openMode ?? 'route';
|
|
3601
|
+
if (effectiveOpenMode === 'route' && action.route) {
|
|
3602
|
+
return false;
|
|
3603
|
+
}
|
|
3604
|
+
const contract = this.launcher.resolveRuntimeContract(action, row, this.resolvedMetadata, {
|
|
3605
|
+
capabilities: this.collectionCapabilities,
|
|
3606
|
+
links: this.resolveCrudRuntimeLinks(action.action, row),
|
|
3607
|
+
});
|
|
3608
|
+
const submitUrl = String(contract.submitUrl || '').trim();
|
|
3609
|
+
const submitMethod = String(contract.submitMethod || 'delete').trim().toUpperCase();
|
|
3610
|
+
if (!submitUrl) {
|
|
3611
|
+
throw new Error('Delete action could not resolve submitUrl.');
|
|
3612
|
+
}
|
|
3613
|
+
if (submitMethod !== 'DELETE') {
|
|
3614
|
+
throw new Error(`Delete action resolved unsupported method "${submitMethod}".`);
|
|
3615
|
+
}
|
|
3616
|
+
const ref = this.dialog.open(ConfirmDialogComponent, {
|
|
3617
|
+
autoFocus: false,
|
|
3618
|
+
restoreFocus: true,
|
|
3619
|
+
data: {
|
|
3620
|
+
title: action.label || this.getCrudActionLabel('delete'),
|
|
3621
|
+
message: this.resolvedMetadata.i18n?.crudDialog?.['deleteConfirmMessage'] ||
|
|
3622
|
+
'Esta ação não pode ser desfeita. Deseja continuar?',
|
|
3623
|
+
confirmText: this.resolvedMetadata.i18n?.crudDialog?.['deleteConfirmButton'] ||
|
|
3624
|
+
this.getCrudActionLabel('delete'),
|
|
3625
|
+
cancelText: this.resolvedMetadata.i18n?.crudDialog?.['deleteCancelButton'] || this.tx('crud.delete.cancel', 'Cancel'),
|
|
3626
|
+
type: 'warning',
|
|
3627
|
+
},
|
|
3628
|
+
});
|
|
3629
|
+
this.afterOpen.emit({ mode: 'modal', action: action.action });
|
|
3630
|
+
const confirmed = await firstValueFrom(ref.afterClosed());
|
|
3631
|
+
this.afterClose.emit();
|
|
3632
|
+
if (!confirmed) {
|
|
3633
|
+
return true;
|
|
3634
|
+
}
|
|
3635
|
+
await firstValueFrom(this.http.request('DELETE', submitUrl));
|
|
3636
|
+
const idField = this.getIdField();
|
|
3637
|
+
const id = row?.[idField];
|
|
3638
|
+
this.afterDelete.emit({ id });
|
|
3639
|
+
this.refreshTable();
|
|
3640
|
+
return true;
|
|
3641
|
+
}
|
|
647
3642
|
refreshTable() {
|
|
648
3643
|
this.table.refetch();
|
|
649
3644
|
}
|
|
@@ -1142,6 +4137,49 @@ class PraxisCrudComponent {
|
|
|
1142
4137
|
openMode: action.openMode,
|
|
1143
4138
|
})),
|
|
1144
4139
|
idField: meta.resource?.idField || 'id',
|
|
4140
|
+
openAuthoring: this.openCrudAuthoringFromTable,
|
|
4141
|
+
};
|
|
4142
|
+
}
|
|
4143
|
+
openCrudAuthoringFromTable = () => {
|
|
4144
|
+
const seed = this.currentAuthoringDocument
|
|
4145
|
+
|| createCrudAuthoringDocument({ metadata: this.resolvedMetadata });
|
|
4146
|
+
const ref = openCrudMetadataEditor(this.settingsPanel, {
|
|
4147
|
+
crudId: this.crudId,
|
|
4148
|
+
document: seed,
|
|
4149
|
+
title: this.tx('crud.authoring.title', 'Configurações do CRUD'),
|
|
4150
|
+
titleIcon: 'table_chart',
|
|
4151
|
+
});
|
|
4152
|
+
ref.applied$
|
|
4153
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4154
|
+
.subscribe((payload) => this.applyCrudAuthoringPayload(payload, 'applied'));
|
|
4155
|
+
ref.saved$
|
|
4156
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4157
|
+
.subscribe((payload) => this.applyCrudAuthoringPayload(payload, 'saved'));
|
|
4158
|
+
};
|
|
4159
|
+
applyCrudAuthoringPayload(payload, eventName) {
|
|
4160
|
+
const next = parseLegacyOrCrudDocument(payload);
|
|
4161
|
+
this.currentAuthoringDocument = next;
|
|
4162
|
+
this.resolvedMetadata = next.metadata;
|
|
4163
|
+
this.tableQueryContext = this.resolveQueryContext(this.resolvedMetadata);
|
|
4164
|
+
this.tableFilterCriteria = this.resolveFilterCriteria(this.resolvedMetadata);
|
|
4165
|
+
this.applyResolvedCrudState(this.resolvedMetadata);
|
|
4166
|
+
this.cdr.markForCheck();
|
|
4167
|
+
const widgetPersistencePayload = this.buildWidgetPersistencePayload(next);
|
|
4168
|
+
if (eventName === 'applied') {
|
|
4169
|
+
this.crudAuthoringDocumentApplied.emit(widgetPersistencePayload);
|
|
4170
|
+
}
|
|
4171
|
+
if (eventName === 'saved') {
|
|
4172
|
+
this.crudAuthoringDocumentSaved.emit(widgetPersistencePayload);
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
buildWidgetPersistencePayload(document) {
|
|
4176
|
+
return {
|
|
4177
|
+
document,
|
|
4178
|
+
metadata: document.metadata,
|
|
4179
|
+
inputPatch: {
|
|
4180
|
+
metadata: document.metadata,
|
|
4181
|
+
'context.authoringDocument': document,
|
|
4182
|
+
},
|
|
1145
4183
|
};
|
|
1146
4184
|
}
|
|
1147
4185
|
resolveCreateToolbarAction(meta, capabilities) {
|
|
@@ -1242,19 +4280,20 @@ class PraxisCrudComponent {
|
|
|
1242
4280
|
}));
|
|
1243
4281
|
}
|
|
1244
4282
|
supportsCreateCapability(snapshot) {
|
|
1245
|
-
return (
|
|
4283
|
+
return (snapshot.operations?.create?.supported === true ||
|
|
4284
|
+
this.hasCanonicalOperation(snapshot, 'create') ||
|
|
1246
4285
|
snapshot.surfaces.some((surface) => surface.scope === 'COLLECTION' &&
|
|
1247
4286
|
this.isWritableCrudSurface(surface) &&
|
|
1248
4287
|
surface.availability?.allowed !== false));
|
|
1249
4288
|
}
|
|
1250
4289
|
supportsViewCapability(snapshot) {
|
|
1251
|
-
return this.hasCanonicalOperation(snapshot, 'byId');
|
|
4290
|
+
return snapshot.operations?.view?.supported === true || this.hasCanonicalOperation(snapshot, 'byId');
|
|
1252
4291
|
}
|
|
1253
4292
|
supportsEditCapability(snapshot) {
|
|
1254
|
-
return this.hasCanonicalOperation(snapshot, 'update');
|
|
4293
|
+
return snapshot.operations?.edit?.supported === true || this.hasCanonicalOperation(snapshot, 'update');
|
|
1255
4294
|
}
|
|
1256
4295
|
supportsDeleteCapability(snapshot) {
|
|
1257
|
-
return this.hasCanonicalOperation(snapshot, 'delete');
|
|
4296
|
+
return snapshot.operations?.delete?.supported === true || this.hasCanonicalOperation(snapshot, 'delete');
|
|
1258
4297
|
}
|
|
1259
4298
|
hasCanonicalOperation(snapshot, operation) {
|
|
1260
4299
|
return snapshot.canonicalOperations?.[operation] === true;
|
|
@@ -1287,6 +4326,13 @@ class PraxisCrudComponent {
|
|
|
1287
4326
|
return null;
|
|
1288
4327
|
}
|
|
1289
4328
|
}
|
|
4329
|
+
resolveCrudRuntimeLinks(action, row) {
|
|
4330
|
+
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
4331
|
+
if (normalizedAction === 'create') {
|
|
4332
|
+
return this.tableCollectionLinks;
|
|
4333
|
+
}
|
|
4334
|
+
return row?._links ?? null;
|
|
4335
|
+
}
|
|
1290
4336
|
isRecord(value) {
|
|
1291
4337
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
1292
4338
|
}
|
|
@@ -1335,7 +4381,7 @@ class PraxisCrudComponent {
|
|
|
1335
4381
|
}
|
|
1336
4382
|
}
|
|
1337
4383
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1338
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", tableRuntimeConfigChange: "tableRuntimeConfigChange" }, providers: [
|
|
4384
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
|
|
1339
4385
|
providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
|
|
1340
4386
|
providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
|
|
1341
4387
|
], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
@@ -1428,6 +4474,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1428
4474
|
type: Output
|
|
1429
4475
|
}], tableRuntimeConfigChange: [{
|
|
1430
4476
|
type: Output
|
|
4477
|
+
}], crudAuthoringDocumentApplied: [{
|
|
4478
|
+
type: Output
|
|
4479
|
+
}], crudAuthoringDocumentSaved: [{
|
|
4480
|
+
type: Output
|
|
1431
4481
|
}], table: [{
|
|
1432
4482
|
type: ViewChild,
|
|
1433
4483
|
args: [PraxisTable]
|
|
@@ -1449,6 +4499,7 @@ class DynamicFormDialogHostComponent {
|
|
|
1449
4499
|
destroyRef = inject(DestroyRef);
|
|
1450
4500
|
resourcePath;
|
|
1451
4501
|
resourceId;
|
|
4502
|
+
initialValue;
|
|
1452
4503
|
schemaUrl;
|
|
1453
4504
|
submitUrl;
|
|
1454
4505
|
submitMethod;
|
|
@@ -1498,6 +4549,7 @@ class DynamicFormDialogHostComponent {
|
|
|
1498
4549
|
}
|
|
1499
4550
|
this.idField = this.data.metadata?.resource?.idField ?? 'id';
|
|
1500
4551
|
this.resourceId = this.data.inputs?.[this.idField];
|
|
4552
|
+
this.initialValue = this.extractInitialValue(this.data.inputs);
|
|
1501
4553
|
this.schemaUrl = this.data.inputs?.['schemaUrl'] ?? null;
|
|
1502
4554
|
this.submitUrl = this.data.inputs?.['submitUrl'] ?? null;
|
|
1503
4555
|
this.submitMethod = this.data.inputs?.['submitMethod'] ?? null;
|
|
@@ -1540,6 +4592,30 @@ class DynamicFormDialogHostComponent {
|
|
|
1540
4592
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1541
4593
|
.subscribe(() => this.saveState());
|
|
1542
4594
|
}
|
|
4595
|
+
extractInitialValue(inputs) {
|
|
4596
|
+
if (!inputs || typeof inputs !== 'object') {
|
|
4597
|
+
return null;
|
|
4598
|
+
}
|
|
4599
|
+
const reserved = new Set([
|
|
4600
|
+
this.idField,
|
|
4601
|
+
'schemaUrl',
|
|
4602
|
+
'submitUrl',
|
|
4603
|
+
'submitMethod',
|
|
4604
|
+
'apiEndpointKey',
|
|
4605
|
+
'apiUrlEntry',
|
|
4606
|
+
'initialValue',
|
|
4607
|
+
]);
|
|
4608
|
+
const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
|
|
4609
|
+
? { ...inputs['initialValue'] }
|
|
4610
|
+
: {};
|
|
4611
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
4612
|
+
if (reserved.has(key) || value === undefined) {
|
|
4613
|
+
continue;
|
|
4614
|
+
}
|
|
4615
|
+
explicit[key] = value;
|
|
4616
|
+
}
|
|
4617
|
+
return Object.keys(explicit).length ? explicit : null;
|
|
4618
|
+
}
|
|
1543
4619
|
ngOnInit() {
|
|
1544
4620
|
// Carregar estado salvo (se habilitado)
|
|
1545
4621
|
if (this.rememberState && this.stateKey) {
|
|
@@ -1658,7 +4734,7 @@ class DynamicFormDialogHostComponent {
|
|
|
1658
4734
|
this.dialogRef.updatePosition();
|
|
1659
4735
|
}
|
|
1660
4736
|
}
|
|
1661
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormDialogHostComponent, deps: [{ token: MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: DialogService }, { token: i2.GenericCrudService }, { token: ASYNC_CONFIG_STORAGE }], target: i0.ɵɵFactoryTarget.Component });
|
|
4737
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormDialogHostComponent, deps: [{ token: MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: DialogService }, { token: i2$1.GenericCrudService }, { token: ASYNC_CONFIG_STORAGE }], target: i0.ɵɵFactoryTarget.Component });
|
|
1662
4738
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: DynamicFormDialogHostComponent, isStandalone: true, selector: "praxis-dynamic-form-dialog-host", host: { properties: { "attr.data-density": "modal.density || \"default\"" }, classAttribute: "praxis-dialog" }, providers: [GenericCrudService], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
|
|
1663
4739
|
<div mat-dialog-title class="dialog-header">
|
|
1664
4740
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
@@ -1694,6 +4770,7 @@ class DynamicFormDialogHostComponent {
|
|
|
1694
4770
|
[formId]="data.action?.formId"
|
|
1695
4771
|
[resourcePath]="resourcePath"
|
|
1696
4772
|
[resourceId]="resourceId"
|
|
4773
|
+
[initialValue]="initialValue"
|
|
1697
4774
|
[mode]="mode"
|
|
1698
4775
|
[schemaUrl]="schemaUrl"
|
|
1699
4776
|
[submitUrl]="submitUrl"
|
|
@@ -1706,7 +4783,7 @@ class DynamicFormDialogHostComponent {
|
|
|
1706
4783
|
(formCancel)="onCancel()"
|
|
1707
4784
|
></praxis-dynamic-form>
|
|
1708
4785
|
</mat-dialog-content>
|
|
1709
|
-
`, isInline: true, styles: [":host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}:host([data-density=\"compact\"]){--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}.dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}.dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}.spacer{flex:1}.dialog-content{overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}.dialog-header button.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}.dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}.dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type:
|
|
4786
|
+
`, isInline: true, styles: [":host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}:host([data-density=\"compact\"]){--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}.dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);padding:0 var(--dlg-pad);height:var(--dlg-header-h);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}.dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}.spacer{flex:1}.dialog-content{overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}.dialog-header button.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}.dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}.dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "schemaSource", "schemaUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "formId", "componentInstanceId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError"] }] });
|
|
1710
4787
|
}
|
|
1711
4788
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
|
|
1712
4789
|
type: Component,
|
|
@@ -1755,6 +4832,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1755
4832
|
[formId]="data.action?.formId"
|
|
1756
4833
|
[resourcePath]="resourcePath"
|
|
1757
4834
|
[resourceId]="resourceId"
|
|
4835
|
+
[initialValue]="initialValue"
|
|
1758
4836
|
[mode]="mode"
|
|
1759
4837
|
[schemaUrl]="schemaUrl"
|
|
1760
4838
|
[submitUrl]="submitUrl"
|
|
@@ -1774,7 +4852,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
1774
4852
|
}] }, { type: undefined, decorators: [{
|
|
1775
4853
|
type: Inject,
|
|
1776
4854
|
args: [MAT_DIALOG_DATA]
|
|
1777
|
-
}] }, { type: DialogService }, { type: i2.GenericCrudService }, { type: undefined, decorators: [{
|
|
4855
|
+
}] }, { type: DialogService }, { type: i2$1.GenericCrudService }, { type: undefined, decorators: [{
|
|
1778
4856
|
type: Inject,
|
|
1779
4857
|
args: [ASYNC_CONFIG_STORAGE]
|
|
1780
4858
|
}] }], propDecorators: { formComp: [{
|
|
@@ -1804,22 +4882,22 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1804
4882
|
{
|
|
1805
4883
|
name: 'crudId',
|
|
1806
4884
|
type: 'string',
|
|
1807
|
-
description: 'Identificador do CRUD (base para tabela/formulário e persistência)',
|
|
4885
|
+
description: 'Identificador do CRUD (base para tabela/formulário e persistência).',
|
|
1808
4886
|
},
|
|
1809
4887
|
{
|
|
1810
4888
|
name: 'componentInstanceId',
|
|
1811
4889
|
type: 'string',
|
|
1812
|
-
description: 'Identificador opcional para múltiplas instâncias na mesma rota',
|
|
4890
|
+
description: 'Identificador opcional para múltiplas instâncias na mesma rota.',
|
|
1813
4891
|
},
|
|
1814
4892
|
{
|
|
1815
4893
|
name: 'context',
|
|
1816
4894
|
type: 'Record<string, unknown>',
|
|
1817
|
-
description: 'Contexto opaco do host. A
|
|
4895
|
+
description: 'Contexto opaco do host. A implementação atual o expõe como Input e o usa para seeds editoriais do authoring.',
|
|
1818
4896
|
},
|
|
1819
4897
|
{
|
|
1820
4898
|
name: 'metadata.queryContext',
|
|
1821
4899
|
type: 'PraxisDataQueryContext | null',
|
|
1822
|
-
description: 'Contexto
|
|
4900
|
+
description: 'Contexto semântico de consulta encaminhado para a tabela interna do CRUD. Preferir este contrato para novo authoring remoto.',
|
|
1823
4901
|
},
|
|
1824
4902
|
{
|
|
1825
4903
|
name: 'metadata.filterCriteria',
|
|
@@ -1835,35 +4913,49 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1835
4913
|
name: 'enableCustomization',
|
|
1836
4914
|
type: 'boolean',
|
|
1837
4915
|
default: false,
|
|
1838
|
-
description: 'Habilita modo de customização do layout',
|
|
4916
|
+
description: 'Habilita modo de customização do layout.',
|
|
1839
4917
|
},
|
|
1840
4918
|
],
|
|
1841
4919
|
outputs: [
|
|
1842
4920
|
{
|
|
1843
4921
|
name: 'afterOpen',
|
|
1844
4922
|
type: '{ mode: FormOpenMode; action: string }',
|
|
1845
|
-
description: 'Emitido após abrir diálogo',
|
|
4923
|
+
description: 'Emitido após abrir diálogo.',
|
|
1846
4924
|
},
|
|
1847
4925
|
{
|
|
1848
4926
|
name: 'afterClose',
|
|
1849
4927
|
type: 'void',
|
|
1850
|
-
description: 'Emitido após fechar diálogo',
|
|
4928
|
+
description: 'Emitido após fechar diálogo.',
|
|
1851
4929
|
},
|
|
1852
4930
|
{
|
|
1853
4931
|
name: 'afterSave',
|
|
1854
4932
|
type: '{ id: string | number; data: unknown }',
|
|
1855
|
-
description: 'Emitido ao salvar',
|
|
4933
|
+
description: 'Emitido ao salvar.',
|
|
1856
4934
|
},
|
|
1857
4935
|
{
|
|
1858
4936
|
name: 'afterDelete',
|
|
1859
4937
|
type: '{ id: string | number }',
|
|
1860
|
-
description: 'Emitido ao deletar',
|
|
4938
|
+
description: 'Emitido ao deletar.',
|
|
4939
|
+
},
|
|
4940
|
+
{
|
|
4941
|
+
name: 'error',
|
|
4942
|
+
type: 'unknown',
|
|
4943
|
+
description: 'Emitido em erros.',
|
|
1861
4944
|
},
|
|
1862
|
-
{ name: 'error', type: 'unknown', description: 'Emitido em erros' },
|
|
1863
4945
|
{
|
|
1864
4946
|
name: 'configureRequested',
|
|
1865
4947
|
type: 'void',
|
|
1866
|
-
description: 'Emitido quando CTA de configuração é acionado em modo edição',
|
|
4948
|
+
description: 'Emitido quando o CTA de configuração é acionado em modo de edição.',
|
|
4949
|
+
},
|
|
4950
|
+
{
|
|
4951
|
+
name: 'crudAuthoringDocumentApplied',
|
|
4952
|
+
type: 'CrudAuthoringWidgetPersistenceEvent',
|
|
4953
|
+
description: 'Emitido quando o editor canônico do CRUD aplica um documento e publica o patch de inputs do widget para hosts como praxis-dynamic-page.',
|
|
4954
|
+
},
|
|
4955
|
+
{
|
|
4956
|
+
name: 'crudAuthoringDocumentSaved',
|
|
4957
|
+
type: 'CrudAuthoringWidgetPersistenceEvent',
|
|
4958
|
+
description: 'Emitido quando o editor canônico do CRUD salva um documento e publica o patch de inputs do widget para hosts como praxis-dynamic-page.',
|
|
1867
4959
|
},
|
|
1868
4960
|
],
|
|
1869
4961
|
actions: [
|
|
@@ -1871,13 +4963,13 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1871
4963
|
id: 'create',
|
|
1872
4964
|
label: 'Criar',
|
|
1873
4965
|
icon: 'add',
|
|
1874
|
-
description: 'Emite evento ao abrir fluxo de criação',
|
|
4966
|
+
description: 'Emite evento ao abrir fluxo de criação.',
|
|
1875
4967
|
emit: 'afterOpen',
|
|
1876
4968
|
payloadSchema: {
|
|
1877
4969
|
type: 'object',
|
|
1878
4970
|
properties: {
|
|
1879
|
-
action: { type: 'string', description: 'Ação executada' },
|
|
1880
|
-
mode: { type: 'string', description: 'Modo de abertura' },
|
|
4971
|
+
action: { type: 'string', description: 'Ação executada.' },
|
|
4972
|
+
mode: { type: 'string', description: 'Modo de abertura.' },
|
|
1881
4973
|
},
|
|
1882
4974
|
required: ['action', 'mode'],
|
|
1883
4975
|
example: { action: 'create', mode: 'route' },
|
|
@@ -1888,13 +4980,13 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1888
4980
|
id: 'view',
|
|
1889
4981
|
label: 'Visualizar',
|
|
1890
4982
|
icon: 'visibility',
|
|
1891
|
-
description: 'Emite evento ao abrir fluxo de visualização',
|
|
4983
|
+
description: 'Emite evento ao abrir fluxo de visualização.',
|
|
1892
4984
|
emit: 'afterOpen',
|
|
1893
4985
|
payloadSchema: {
|
|
1894
4986
|
type: 'object',
|
|
1895
4987
|
properties: {
|
|
1896
|
-
action: { type: 'string', description: 'Ação executada' },
|
|
1897
|
-
mode: { type: 'string', description: 'Modo de abertura' },
|
|
4988
|
+
action: { type: 'string', description: 'Ação executada.' },
|
|
4989
|
+
mode: { type: 'string', description: 'Modo de abertura.' },
|
|
1898
4990
|
},
|
|
1899
4991
|
required: ['action', 'mode'],
|
|
1900
4992
|
example: { action: 'view', mode: 'modal' },
|
|
@@ -1905,13 +4997,13 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1905
4997
|
id: 'edit',
|
|
1906
4998
|
label: 'Editar',
|
|
1907
4999
|
icon: 'edit',
|
|
1908
|
-
description: 'Emite evento ao abrir fluxo de edição',
|
|
5000
|
+
description: 'Emite evento ao abrir fluxo de edição.',
|
|
1909
5001
|
emit: 'afterOpen',
|
|
1910
5002
|
payloadSchema: {
|
|
1911
5003
|
type: 'object',
|
|
1912
5004
|
properties: {
|
|
1913
|
-
action: { type: 'string', description: 'Ação executada' },
|
|
1914
|
-
mode: { type: 'string', description: 'Modo de abertura' },
|
|
5005
|
+
action: { type: 'string', description: 'Ação executada.' },
|
|
5006
|
+
mode: { type: 'string', description: 'Modo de abertura.' },
|
|
1915
5007
|
},
|
|
1916
5008
|
required: ['action', 'mode'],
|
|
1917
5009
|
example: { action: 'edit', mode: 'drawer' },
|
|
@@ -1922,12 +5014,12 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1922
5014
|
id: 'delete',
|
|
1923
5015
|
label: 'Excluir',
|
|
1924
5016
|
icon: 'delete',
|
|
1925
|
-
description: 'Emite evento após exclusão',
|
|
5017
|
+
description: 'Emite evento após exclusão.',
|
|
1926
5018
|
emit: 'afterDelete',
|
|
1927
5019
|
payloadSchema: {
|
|
1928
5020
|
type: 'object',
|
|
1929
5021
|
properties: {
|
|
1930
|
-
id: { type: 'string | number', description: 'ID do registro (string ou number)' },
|
|
5022
|
+
id: { type: 'string | number', description: 'ID do registro (string ou number).' },
|
|
1931
5023
|
},
|
|
1932
5024
|
required: ['id'],
|
|
1933
5025
|
example: { id: '123' },
|
|
@@ -1938,13 +5030,13 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1938
5030
|
id: 'save',
|
|
1939
5031
|
label: 'Salvar',
|
|
1940
5032
|
icon: 'save',
|
|
1941
|
-
description: 'Emite evento após salvar',
|
|
5033
|
+
description: 'Emite evento após salvar.',
|
|
1942
5034
|
emit: 'afterSave',
|
|
1943
5035
|
payloadSchema: {
|
|
1944
5036
|
type: 'object',
|
|
1945
5037
|
properties: {
|
|
1946
|
-
id: { type: 'string | number', description: 'ID do registro (string ou number)' },
|
|
1947
|
-
data: { type: 'object', description: 'Dados salvos' },
|
|
5038
|
+
id: { type: 'string | number', description: 'ID do registro (string ou number).' },
|
|
5039
|
+
data: { type: 'object', description: 'Dados salvos.' },
|
|
1948
5040
|
},
|
|
1949
5041
|
required: ['id', 'data'],
|
|
1950
5042
|
example: { id: '123', data: {} },
|
|
@@ -1955,7 +5047,7 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1955
5047
|
id: 'close',
|
|
1956
5048
|
label: 'Fechar',
|
|
1957
5049
|
icon: 'close',
|
|
1958
|
-
description: 'Emite evento ao fechar o diálogo',
|
|
5050
|
+
description: 'Emite evento ao fechar o diálogo.',
|
|
1959
5051
|
emit: 'afterClose',
|
|
1960
5052
|
scope: 'shell',
|
|
1961
5053
|
},
|
|
@@ -1963,7 +5055,7 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
1963
5055
|
id: 'configure',
|
|
1964
5056
|
label: 'Configurar',
|
|
1965
5057
|
icon: 'tune',
|
|
1966
|
-
description: 'Emite evento ao abrir configuração',
|
|
5058
|
+
description: 'Emite evento ao abrir configuração.',
|
|
1967
5059
|
emit: 'configureRequested',
|
|
1968
5060
|
scope: 'shell',
|
|
1969
5061
|
},
|
|
@@ -2018,7 +5110,7 @@ class CrudPageHeaderComponent {
|
|
|
2018
5110
|
<ng-content></ng-content>
|
|
2019
5111
|
</div>
|
|
2020
5112
|
</header>
|
|
2021
|
-
`, isInline: true, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface)}.crud-header.sticky{position:sticky;top:0;z-index:10;-webkit-backdrop-filter:saturate(110%);backdrop-filter:saturate(110%)}.crud-header.with-divider{border-bottom:1px solid var(--md-sys-color-outline-variant)}.left{display:flex;align-items:center;gap:8px;min-width:0}.right{display:flex;align-items:center;gap:8px}.title{margin:0;font-weight:600;color:var(--md-sys-color-on-surface, currentColor);font:var(--mdc-typography-title-large, 600 20px/28px system-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.back-btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.back-btn .mat-mdc-button{padding-left:0}.back-btn mat-icon{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn .label{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn.ghost{border-radius:20px;padding:4px 8px}.back-btn.ghost:hover{background:var(--md-sys-color-primary-container)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container);box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant)}.back-btn.tonal:hover{background:var(--md-sys-color-primary-container)}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.back-btn.outlined:hover{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}@media(max-width:599px){.label.hide-on-narrow{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$
|
|
5113
|
+
`, isInline: true, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface)}.crud-header.sticky{position:sticky;top:0;z-index:10;-webkit-backdrop-filter:saturate(110%);backdrop-filter:saturate(110%)}.crud-header.with-divider{border-bottom:1px solid var(--md-sys-color-outline-variant)}.left{display:flex;align-items:center;gap:8px;min-width:0}.right{display:flex;align-items:center;gap:8px}.title{margin:0;font-weight:600;color:var(--md-sys-color-on-surface, currentColor);font:var(--mdc-typography-title-large, 600 20px/28px system-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.back-btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.back-btn .mat-mdc-button{padding-left:0}.back-btn mat-icon{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn .label{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn.ghost{border-radius:20px;padding:4px 8px}.back-btn.ghost:hover{background:var(--md-sys-color-primary-container)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container);box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant)}.back-btn.tonal:hover{background:var(--md-sys-color-primary-container)}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.back-btn.outlined:hover{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}@media(max-width:599px){.label.hide-on-narrow{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
|
|
2022
5114
|
}
|
|
2023
5115
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
|
|
2024
5116
|
type: Component,
|
|
@@ -2145,6 +5237,7 @@ const CRUD_AI_CAPABILITIES = {
|
|
|
2145
5237
|
{ path: 'actions[].params[].from', category: 'actions', valueKind: 'string', description: 'Origem do parametro.' },
|
|
2146
5238
|
{ path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
|
|
2147
5239
|
{ path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
|
|
5240
|
+
{ path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
|
|
2148
5241
|
{ path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
|
|
2149
5242
|
{ path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
|
|
2150
5243
|
{ path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
|
|
@@ -2163,4 +5256,4 @@ const CRUD_AI_CAPABILITIES = {
|
|
|
2163
5256
|
* Generated bundle index. Do not edit.
|
|
2164
5257
|
*/
|
|
2165
5258
|
|
|
2166
|
-
export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, assertCrudMetadata, providePraxisCrudMetadata };
|
|
5259
|
+
export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudMetadataEditorComponent, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, assertCrudMetadata, createCrudAuthoringDocument, findCrudAction, normalizeCrudAuthoringDocument, openCrudMetadataEditor, parseLegacyOrCrudDocument, providePraxisCrudMetadata, serializeCrudAuthoringDocument, validateCrudAuthoringDocument };
|