@praxisui/core 9.0.4-rc.18 → 9.0.4-rc.19
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 +2 -0
- package/ai/component-registry.json +2 -2
- package/fesm2022/praxisui-core.mjs +631 -542
- package/package.json +1 -1
- package/types/praxisui-core.d.ts +14 -0
|
@@ -14021,8 +14021,341 @@ const SURFACE_OPEN_PRESETS = [
|
|
|
14021
14021
|
},
|
|
14022
14022
|
];
|
|
14023
14023
|
|
|
14024
|
+
const RELATED_RESOURCE_OUTLET_I18N_NAMESPACE = 'relatedResourceOutlet';
|
|
14025
|
+
const RELATED_RESOURCE_OUTLET_I18N_CONFIG = {
|
|
14026
|
+
namespaces: {
|
|
14027
|
+
[RELATED_RESOURCE_OUTLET_I18N_NAMESPACE]: {
|
|
14028
|
+
'pt-BR': {
|
|
14029
|
+
'state.idle.title': 'Recurso relacionado não selecionado',
|
|
14030
|
+
'state.idle.description': 'Selecione uma surface relacionada para carregar os dados.',
|
|
14031
|
+
'state.resolving.title': 'Resolvendo recurso relacionado',
|
|
14032
|
+
'state.resolving.description': 'Validando metadados e permissões da relação.',
|
|
14033
|
+
'state.loading.title': 'Carregando recurso relacionado',
|
|
14034
|
+
'state.loading.description': 'Aguarde enquanto a coleção relacionada é carregada.',
|
|
14035
|
+
'state.empty.title': 'Nenhuma operação de leitura publicada',
|
|
14036
|
+
'state.empty.description': 'A relação existe, mas não há operação de lista ou filtro disponível.',
|
|
14037
|
+
'state.permission-limited.title': 'Acesso limitado',
|
|
14038
|
+
'state.permission-limited.description': 'O contexto atual não permite abrir este recurso relacionado.',
|
|
14039
|
+
'state.not-found.title': 'Relação indisponível',
|
|
14040
|
+
'state.not-found.description': 'Não foi possível resolver a relação ou o identificador do registro pai.',
|
|
14041
|
+
'state.error.title': 'Falha ao preparar recurso relacionado',
|
|
14042
|
+
'state.error.description': 'Ocorreu um erro ao preparar a superfície relacionada.',
|
|
14043
|
+
'emptyState.related.title': 'Sem registros em {label}',
|
|
14044
|
+
'emptyState.related.description': 'Esta coleção relacionada não possui registros para o contexto selecionado.',
|
|
14045
|
+
'emptyState.related.descriptionWithAction': 'Use a ação principal para adicionar um registro relacionado quando houver informações para registrar.',
|
|
14046
|
+
'emptyState.related.action.create': 'Adicionar registro',
|
|
14047
|
+
'action.open': 'Abrir relacionado',
|
|
14048
|
+
'status.ready': 'Recurso relacionado pronto',
|
|
14049
|
+
},
|
|
14050
|
+
'en-US': {
|
|
14051
|
+
'state.idle.title': 'Related resource not selected',
|
|
14052
|
+
'state.idle.description': 'Select a related surface to load its data.',
|
|
14053
|
+
'state.resolving.title': 'Resolving related resource',
|
|
14054
|
+
'state.resolving.description': 'Validating relation metadata and permissions.',
|
|
14055
|
+
'state.loading.title': 'Loading related resource',
|
|
14056
|
+
'state.loading.description': 'Wait while the related collection is loaded.',
|
|
14057
|
+
'state.empty.title': 'No read operation published',
|
|
14058
|
+
'state.empty.description': 'The relation exists, but no list or filter operation is available.',
|
|
14059
|
+
'state.permission-limited.title': 'Limited access',
|
|
14060
|
+
'state.permission-limited.description': 'The current context cannot open this related resource.',
|
|
14061
|
+
'state.not-found.title': 'Relation unavailable',
|
|
14062
|
+
'state.not-found.description': 'The relation or parent record identifier could not be resolved.',
|
|
14063
|
+
'state.error.title': 'Failed to prepare related resource',
|
|
14064
|
+
'state.error.description': 'An error occurred while preparing the related surface.',
|
|
14065
|
+
'emptyState.related.title': 'No records in {label}',
|
|
14066
|
+
'emptyState.related.description': 'This related collection has no records for the selected context.',
|
|
14067
|
+
'emptyState.related.descriptionWithAction': 'Use the primary action to add a related record when there is information to capture.',
|
|
14068
|
+
'emptyState.related.action.create': 'Add record',
|
|
14069
|
+
'action.open': 'Open related',
|
|
14070
|
+
'status.ready': 'Related resource ready',
|
|
14071
|
+
},
|
|
14072
|
+
},
|
|
14073
|
+
},
|
|
14074
|
+
};
|
|
14075
|
+
|
|
14076
|
+
class RelatedResourceSurfaceResolverService {
|
|
14077
|
+
i18n = inject(PraxisI18nService, { optional: true });
|
|
14078
|
+
resolve(request) {
|
|
14079
|
+
try {
|
|
14080
|
+
if (!request?.surface) {
|
|
14081
|
+
return { state: 'idle', reason: 'surface-not-provided' };
|
|
14082
|
+
}
|
|
14083
|
+
const surface = request.surface;
|
|
14084
|
+
if (surface.availability?.allowed === false) {
|
|
14085
|
+
return {
|
|
14086
|
+
state: 'permission-limited',
|
|
14087
|
+
reason: surface.availability.reason || 'surface-not-allowed',
|
|
14088
|
+
surface,
|
|
14089
|
+
relatedResource: surface.relatedResource ?? null,
|
|
14090
|
+
};
|
|
14091
|
+
}
|
|
14092
|
+
const relatedResource = surface.relatedResource ?? null;
|
|
14093
|
+
if (!this.isCompleteRelatedResource(relatedResource)) {
|
|
14094
|
+
return {
|
|
14095
|
+
state: 'not-found',
|
|
14096
|
+
reason: 'related-resource-not-published',
|
|
14097
|
+
surface,
|
|
14098
|
+
relatedResource,
|
|
14099
|
+
};
|
|
14100
|
+
}
|
|
14101
|
+
if (!this.hasReadOperation(relatedResource)) {
|
|
14102
|
+
return {
|
|
14103
|
+
state: 'empty',
|
|
14104
|
+
reason: 'related-resource-read-operation-not-published',
|
|
14105
|
+
surface,
|
|
14106
|
+
relatedResource,
|
|
14107
|
+
};
|
|
14108
|
+
}
|
|
14109
|
+
const parentResourceId = this.resolveParentResourceId(request, relatedResource);
|
|
14110
|
+
if (parentResourceId == null || parentResourceId === '') {
|
|
14111
|
+
return {
|
|
14112
|
+
state: 'not-found',
|
|
14113
|
+
reason: 'parent-resource-id-not-resolved',
|
|
14114
|
+
surface,
|
|
14115
|
+
relatedResource,
|
|
14116
|
+
};
|
|
14117
|
+
}
|
|
14118
|
+
const queryContext = this.buildQueryContext(request.queryContext, relatedResource.childParentField, parentResourceId);
|
|
14119
|
+
const payload = this.buildPayload(surface, relatedResource, parentResourceId, queryContext, request);
|
|
14120
|
+
return {
|
|
14121
|
+
state: 'ready',
|
|
14122
|
+
surface,
|
|
14123
|
+
relatedResource,
|
|
14124
|
+
parentResourceId,
|
|
14125
|
+
childResourcePath: relatedResource.childResourcePath,
|
|
14126
|
+
childResourceKey: relatedResource.childResourceKey,
|
|
14127
|
+
queryContext,
|
|
14128
|
+
payload,
|
|
14129
|
+
};
|
|
14130
|
+
}
|
|
14131
|
+
catch (error) {
|
|
14132
|
+
return {
|
|
14133
|
+
state: 'error',
|
|
14134
|
+
reason: error instanceof Error ? error.message : 'related-resource-resolution-failed',
|
|
14135
|
+
surface: request?.surface ?? null,
|
|
14136
|
+
relatedResource: request?.surface?.relatedResource ?? null,
|
|
14137
|
+
};
|
|
14138
|
+
}
|
|
14139
|
+
}
|
|
14140
|
+
state(state, reason) {
|
|
14141
|
+
return { state, reason };
|
|
14142
|
+
}
|
|
14143
|
+
buildPayload(surface, relatedResource, parentResourceId, queryContext, request) {
|
|
14144
|
+
const preset = SURFACE_OPEN_PRESETS.find((candidate) => candidate.id === 'praxis-table');
|
|
14145
|
+
if (!preset) {
|
|
14146
|
+
throw new Error('Missing canonical surface preset "praxis-table".');
|
|
14147
|
+
}
|
|
14148
|
+
const payload = this.clone(preset.payload);
|
|
14149
|
+
payload.presentation = request.presentation ?? payload.presentation;
|
|
14150
|
+
payload.title = request.title || surface.title;
|
|
14151
|
+
payload.subtitle = request.subtitle || surface.description || undefined;
|
|
14152
|
+
payload.icon = request.icon || payload.icon;
|
|
14153
|
+
payload.widget.inputs = {
|
|
14154
|
+
...(payload.widget.inputs || {}),
|
|
14155
|
+
configPersistenceStrategy: 'volatile',
|
|
14156
|
+
resourcePath: this.normalizeResourcePath(relatedResource.childResourcePath),
|
|
14157
|
+
apiEndpointKey: request.apiEndpointKey ?? null,
|
|
14158
|
+
apiUrlEntry: request.apiUrlEntry ?? null,
|
|
14159
|
+
tableId: request.tableId || this.buildStableTableId(surface, relatedResource, parentResourceId),
|
|
14160
|
+
config: this.buildTableConfig(surface, relatedResource, request),
|
|
14161
|
+
queryContext,
|
|
14162
|
+
enableCustomization: request.enableCustomization === true,
|
|
14163
|
+
...(this.trim(request.authoringCapability)
|
|
14164
|
+
? { authoringCapability: this.trim(request.authoringCapability) }
|
|
14165
|
+
: {}),
|
|
14166
|
+
};
|
|
14167
|
+
payload.context = {
|
|
14168
|
+
...(payload.context || {}),
|
|
14169
|
+
resource: {
|
|
14170
|
+
resourceKey: surface.resourceKey,
|
|
14171
|
+
resourcePath: this.normalizeResourcePath(request.parentResourcePath || ''),
|
|
14172
|
+
resourceId: parentResourceId,
|
|
14173
|
+
},
|
|
14174
|
+
surface,
|
|
14175
|
+
relatedResource,
|
|
14176
|
+
childResource: {
|
|
14177
|
+
resourceKey: relatedResource.childResourceKey,
|
|
14178
|
+
resourcePath: this.normalizeResourcePath(relatedResource.childResourcePath),
|
|
14179
|
+
parentField: relatedResource.childParentField,
|
|
14180
|
+
selectable: relatedResource.selectable,
|
|
14181
|
+
selectionKeyField: relatedResource.selectionKeyField,
|
|
14182
|
+
operations: relatedResource.childOperations,
|
|
14183
|
+
},
|
|
14184
|
+
};
|
|
14185
|
+
return payload;
|
|
14186
|
+
}
|
|
14187
|
+
buildTableConfig(surface, relatedResource, request) {
|
|
14188
|
+
const tableConfig = request.tableConfig;
|
|
14189
|
+
const emptyState = request.emptyState;
|
|
14190
|
+
const hasTableConfig = !!tableConfig && typeof tableConfig === 'object' && !Array.isArray(tableConfig);
|
|
14191
|
+
const hasEmptyState = !!emptyState && typeof emptyState === 'object' && !Array.isArray(emptyState);
|
|
14192
|
+
const base = hasTableConfig ? tableConfig : {};
|
|
14193
|
+
const toolbar = this.objectValue(base['toolbar']);
|
|
14194
|
+
const columnsVisibility = this.objectValue(toolbar['columnsVisibility']);
|
|
14195
|
+
const behavior = this.objectValue(base['behavior']);
|
|
14196
|
+
const currentEmptyState = this.objectValue(behavior['emptyState']);
|
|
14197
|
+
const hasCurrentEmptyState = Object.keys(currentEmptyState).length > 0;
|
|
14198
|
+
const resolvedEmptyState = hasEmptyState
|
|
14199
|
+
? {
|
|
14200
|
+
...currentEmptyState,
|
|
14201
|
+
...emptyState,
|
|
14202
|
+
}
|
|
14203
|
+
: hasCurrentEmptyState
|
|
14204
|
+
? currentEmptyState
|
|
14205
|
+
: this.buildRelatedEmptyState(surface, relatedResource, request);
|
|
14206
|
+
if (!hasTableConfig && !resolvedEmptyState) {
|
|
14207
|
+
return undefined;
|
|
14208
|
+
}
|
|
14209
|
+
return {
|
|
14210
|
+
...base,
|
|
14211
|
+
toolbar: {
|
|
14212
|
+
...toolbar,
|
|
14213
|
+
columnsVisibility: {
|
|
14214
|
+
enabled: false,
|
|
14215
|
+
...columnsVisibility,
|
|
14216
|
+
},
|
|
14217
|
+
},
|
|
14218
|
+
behavior: {
|
|
14219
|
+
...behavior,
|
|
14220
|
+
emptyState: resolvedEmptyState,
|
|
14221
|
+
},
|
|
14222
|
+
};
|
|
14223
|
+
}
|
|
14224
|
+
buildRelatedEmptyState(surface, relatedResource, request) {
|
|
14225
|
+
const label = this.trim(request.title)
|
|
14226
|
+
|| this.trim(surface.title)
|
|
14227
|
+
|| this.humanizeResourceKey(relatedResource.childResourceKey);
|
|
14228
|
+
const canCreate = relatedResource.childOperations.includes('CREATE');
|
|
14229
|
+
const descriptionKey = canCreate
|
|
14230
|
+
? 'emptyState.related.descriptionWithAction'
|
|
14231
|
+
: 'emptyState.related.description';
|
|
14232
|
+
const actions = canCreate
|
|
14233
|
+
? [
|
|
14234
|
+
{
|
|
14235
|
+
label: this.t('emptyState.related.action.create', 'Adicionar registro'),
|
|
14236
|
+
action: 'create',
|
|
14237
|
+
icon: 'add',
|
|
14238
|
+
primary: true,
|
|
14239
|
+
},
|
|
14240
|
+
]
|
|
14241
|
+
: [];
|
|
14242
|
+
return {
|
|
14243
|
+
title: this.t('emptyState.related.title', 'Sem registros em {label}', { label }),
|
|
14244
|
+
message: this.t(descriptionKey, canCreate
|
|
14245
|
+
? 'Use a ação principal para adicionar um registro relacionado quando houver informações para registrar.'
|
|
14246
|
+
: 'Esta coleção relacionada não possui registros para o contexto selecionado.', { label }),
|
|
14247
|
+
icon: this.trim(request.icon) || 'hub',
|
|
14248
|
+
tone: 'neutral',
|
|
14249
|
+
variant: 'inline',
|
|
14250
|
+
density: 'compact',
|
|
14251
|
+
alignment: 'center',
|
|
14252
|
+
iconContainer: 'soft',
|
|
14253
|
+
actions,
|
|
14254
|
+
};
|
|
14255
|
+
}
|
|
14256
|
+
objectValue(value) {
|
|
14257
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
14258
|
+
? value
|
|
14259
|
+
: {};
|
|
14260
|
+
}
|
|
14261
|
+
buildQueryContext(queryContext, childParentField, parentResourceId) {
|
|
14262
|
+
return {
|
|
14263
|
+
...(queryContext || {}),
|
|
14264
|
+
filters: {
|
|
14265
|
+
...(queryContext?.filters || {}),
|
|
14266
|
+
[childParentField]: parentResourceId,
|
|
14267
|
+
},
|
|
14268
|
+
meta: {
|
|
14269
|
+
...(queryContext?.meta || {}),
|
|
14270
|
+
relatedResource: true,
|
|
14271
|
+
parentFilterField: childParentField,
|
|
14272
|
+
},
|
|
14273
|
+
};
|
|
14274
|
+
}
|
|
14275
|
+
resolveParentResourceId(request, relatedResource) {
|
|
14276
|
+
if (request.parentResourceId != null) {
|
|
14277
|
+
return request.parentResourceId;
|
|
14278
|
+
}
|
|
14279
|
+
return this.readPath(request.parentRecord, relatedResource.parentIdPathVariable);
|
|
14280
|
+
}
|
|
14281
|
+
readPath(record, path) {
|
|
14282
|
+
if (!record || !path) {
|
|
14283
|
+
return null;
|
|
14284
|
+
}
|
|
14285
|
+
const value = path.split('.').reduce((current, segment) => {
|
|
14286
|
+
if (!current || typeof current !== 'object' || Array.isArray(current)) {
|
|
14287
|
+
return undefined;
|
|
14288
|
+
}
|
|
14289
|
+
return current[segment];
|
|
14290
|
+
}, record);
|
|
14291
|
+
return typeof value === 'string' || typeof value === 'number' ? value : null;
|
|
14292
|
+
}
|
|
14293
|
+
isCompleteRelatedResource(relatedResource) {
|
|
14294
|
+
return !!relatedResource
|
|
14295
|
+
&& !!this.trim(relatedResource.childResourceKey)
|
|
14296
|
+
&& !!this.trim(relatedResource.childResourcePath)
|
|
14297
|
+
&& !!this.trim(relatedResource.childParentField)
|
|
14298
|
+
&& !!this.trim(relatedResource.parentIdPathVariable)
|
|
14299
|
+
&& Array.isArray(relatedResource.childOperations);
|
|
14300
|
+
}
|
|
14301
|
+
hasReadOperation(relatedResource) {
|
|
14302
|
+
return relatedResource.childOperations.includes('LIST')
|
|
14303
|
+
|| relatedResource.childOperations.includes('FILTER');
|
|
14304
|
+
}
|
|
14305
|
+
buildStableTableId(surface, relatedResource, parentResourceId) {
|
|
14306
|
+
return this.sanitizeStableId(`${relatedResource.childResourceKey}.${surface.id}.${String(parentResourceId)}`);
|
|
14307
|
+
}
|
|
14308
|
+
normalizeResourcePath(resourcePath) {
|
|
14309
|
+
let normalized = this.trim(resourcePath);
|
|
14310
|
+
if (/^https?:\/\//i.test(normalized)) {
|
|
14311
|
+
try {
|
|
14312
|
+
normalized = new URL(normalized).pathname;
|
|
14313
|
+
}
|
|
14314
|
+
catch {
|
|
14315
|
+
return '';
|
|
14316
|
+
}
|
|
14317
|
+
}
|
|
14318
|
+
return normalized
|
|
14319
|
+
.replace(/^\/+/, '')
|
|
14320
|
+
.replace(/^(?:api\/)+/i, '')
|
|
14321
|
+
.replace(/\/+$/, '');
|
|
14322
|
+
}
|
|
14323
|
+
sanitizeStableId(value) {
|
|
14324
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
14325
|
+
}
|
|
14326
|
+
humanizeResourceKey(value) {
|
|
14327
|
+
const lastSegment = this.trim(value).split(/[./_-]+/).filter(Boolean).pop() || 'registros';
|
|
14328
|
+
return lastSegment
|
|
14329
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
14330
|
+
.replace(/\s+/g, ' ')
|
|
14331
|
+
.trim();
|
|
14332
|
+
}
|
|
14333
|
+
t(key, fallback, params) {
|
|
14334
|
+
if (this.i18n) {
|
|
14335
|
+
return this.interpolate(this.i18n.t(key, params, fallback, RELATED_RESOURCE_OUTLET_I18N_NAMESPACE), params);
|
|
14336
|
+
}
|
|
14337
|
+
return this.interpolate(fallback, params);
|
|
14338
|
+
}
|
|
14339
|
+
interpolate(template, params) {
|
|
14340
|
+
return Object.entries(params || {}).reduce((current, [name, value]) => current.replace(new RegExp(`\\{${name}\\}`, 'g'), String(value ?? '')), template);
|
|
14341
|
+
}
|
|
14342
|
+
trim(value) {
|
|
14343
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
14344
|
+
}
|
|
14345
|
+
clone(value) {
|
|
14346
|
+
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
14347
|
+
}
|
|
14348
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RelatedResourceSurfaceResolverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
14349
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RelatedResourceSurfaceResolverService, providedIn: 'any' });
|
|
14350
|
+
}
|
|
14351
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: RelatedResourceSurfaceResolverService, decorators: [{
|
|
14352
|
+
type: Injectable,
|
|
14353
|
+
args: [{ providedIn: 'any' }]
|
|
14354
|
+
}] });
|
|
14355
|
+
|
|
14024
14356
|
class ResourceSurfaceOpenAdapterService {
|
|
14025
14357
|
discovery = inject(ResourceDiscoveryService);
|
|
14358
|
+
relatedResourceResolver = inject(RelatedResourceSurfaceResolverService);
|
|
14026
14359
|
toPayload(surface, options) {
|
|
14027
14360
|
const resourcePath = this.normalizeResourcePath(options.resourcePath);
|
|
14028
14361
|
if (!resourcePath) {
|
|
@@ -14035,6 +14368,25 @@ class ResourceSurfaceOpenAdapterService {
|
|
|
14035
14368
|
}
|
|
14036
14369
|
: undefined;
|
|
14037
14370
|
const resolvedApiEntry = this.discovery.resolveApiEntry(discoveryOptions);
|
|
14371
|
+
const icon = options.icon ?? this.resolveDefaultIcon(surface);
|
|
14372
|
+
if (surface.relatedResource) {
|
|
14373
|
+
const resolution = this.relatedResourceResolver.resolve({
|
|
14374
|
+
surface,
|
|
14375
|
+
parentResourceId: options.resourceId ?? null,
|
|
14376
|
+
parentResourcePath: resourcePath,
|
|
14377
|
+
presentation: options.presentation,
|
|
14378
|
+
title: options.title ?? surface.title,
|
|
14379
|
+
subtitle: options.subtitle ?? surface.description ?? null,
|
|
14380
|
+
icon,
|
|
14381
|
+
queryContext: options.queryContext,
|
|
14382
|
+
apiEndpointKey: options.endpointKey ?? null,
|
|
14383
|
+
apiUrlEntry: options.apiUrlEntry ?? resolvedApiEntry,
|
|
14384
|
+
});
|
|
14385
|
+
if (resolution.state !== 'ready' || !resolution.payload) {
|
|
14386
|
+
throw new Error(`ResourceSurfaceOpenAdapterService could not materialize related surface "${surface.id}": ${resolution.reason || resolution.state}.`);
|
|
14387
|
+
}
|
|
14388
|
+
return resolution.payload;
|
|
14389
|
+
}
|
|
14038
14390
|
const resolvedSchemaUrl = this.discovery.resolveHref(surface.schemaUrl, discoveryOptions);
|
|
14039
14391
|
const resolvedSubmitUrl = this.isWritableFormSurface(surface.kind)
|
|
14040
14392
|
? this.discovery.resolveHref(surface.path, discoveryOptions)
|
|
@@ -14046,7 +14398,7 @@ class ResourceSurfaceOpenAdapterService {
|
|
|
14046
14398
|
presentation: options.presentation ?? basePayload.presentation,
|
|
14047
14399
|
title: options.title ?? surface.title ?? basePayload.title,
|
|
14048
14400
|
subtitle: options.subtitle ?? surface.description ?? basePayload.subtitle,
|
|
14049
|
-
icon:
|
|
14401
|
+
icon: icon ?? basePayload.icon,
|
|
14050
14402
|
context: {
|
|
14051
14403
|
resource: {
|
|
14052
14404
|
resourceKey: surface.resourceKey,
|
|
@@ -14135,6 +14487,15 @@ class ResourceSurfaceOpenAdapterService {
|
|
|
14135
14487
|
}
|
|
14136
14488
|
return this.clone(preset.payload);
|
|
14137
14489
|
}
|
|
14490
|
+
resolveDefaultIcon(surface) {
|
|
14491
|
+
if (surface.relatedResource) {
|
|
14492
|
+
return 'dataset_linked';
|
|
14493
|
+
}
|
|
14494
|
+
if (surface.kind === 'VIEW' || surface.kind === 'READ_PROJECTION') {
|
|
14495
|
+
return 'visibility';
|
|
14496
|
+
}
|
|
14497
|
+
return surface.scope === 'COLLECTION' ? 'add' : 'edit_note';
|
|
14498
|
+
}
|
|
14138
14499
|
buildStableInstanceId(surface) {
|
|
14139
14500
|
return `${surface.resourceKey}.${surface.id}`.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
14140
14501
|
}
|
|
@@ -14959,558 +15320,226 @@ class PraxisRuntimeComponentObservationRegistryService {
|
|
|
14959
15320
|
return clonePraxisRuntimeComponentObservation(observation);
|
|
14960
15321
|
}
|
|
14961
15322
|
isActiveObservation(observation) {
|
|
14962
|
-
if (!observation.lifecycle.active) {
|
|
14963
|
-
return false;
|
|
14964
|
-
}
|
|
14965
|
-
const ttlMs = observation.lifecycle.ttlMs;
|
|
14966
|
-
if (ttlMs === undefined || ttlMs < 0) {
|
|
14967
|
-
return true;
|
|
14968
|
-
}
|
|
14969
|
-
const capturedAt = Date.parse(observation.lifecycle.capturedAt);
|
|
14970
|
-
return Number.isFinite(capturedAt) && Date.now() - capturedAt <= ttlMs;
|
|
14971
|
-
}
|
|
14972
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisRuntimeComponentObservationRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
14973
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisRuntimeComponentObservationRegistryService, providedIn: 'root' });
|
|
14974
|
-
}
|
|
14975
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisRuntimeComponentObservationRegistryService, decorators: [{
|
|
14976
|
-
type: Injectable,
|
|
14977
|
-
args: [{ providedIn: 'root' }]
|
|
14978
|
-
}] });
|
|
14979
|
-
function registerPraxisRuntimeComponentObservation(provider, options = {}) {
|
|
14980
|
-
const registry = inject(PraxisRuntimeComponentObservationRegistryService);
|
|
14981
|
-
const destroyRef = options.destroyRef ?? inject(DestroyRef);
|
|
14982
|
-
return registry.register(provider, { ...options, destroyRef });
|
|
14983
|
-
}
|
|
14984
|
-
|
|
14985
|
-
class ResourceActionOpenAdapterService {
|
|
14986
|
-
discovery = inject(ResourceDiscoveryService);
|
|
14987
|
-
toPayload(action, options) {
|
|
14988
|
-
const resourcePath = this.normalizeResourcePath(options.resourcePath);
|
|
14989
|
-
if (!resourcePath) {
|
|
14990
|
-
throw new Error('ResourceActionOpenAdapterService requires resourcePath.');
|
|
14991
|
-
}
|
|
14992
|
-
if (!action.requestSchemaUrl) {
|
|
14993
|
-
throw new Error(`ResourceActionOpenAdapterService requires requestSchemaUrl for action "${action.id}".`);
|
|
14994
|
-
}
|
|
14995
|
-
const discoveryOptions = options.endpointKey || options.apiUrlEntry
|
|
14996
|
-
? {
|
|
14997
|
-
...(options.endpointKey ? { endpointKey: options.endpointKey } : {}),
|
|
14998
|
-
...(options.apiUrlEntry ? { apiUrlEntry: options.apiUrlEntry } : {}),
|
|
14999
|
-
}
|
|
15000
|
-
: undefined;
|
|
15001
|
-
const resolvedRequestSchemaUrl = this.discovery.resolveHref(action.requestSchemaUrl, discoveryOptions);
|
|
15002
|
-
const resolvedResponseSchemaUrl = action.responseSchemaUrl
|
|
15003
|
-
? this.discovery.resolveHref(action.responseSchemaUrl, discoveryOptions)
|
|
15004
|
-
: null;
|
|
15005
|
-
const resolvedSubmitUrl = this.discovery.resolveHref(action.path, discoveryOptions);
|
|
15006
|
-
const payload = this.clone(this.resolveDynamicFormPreset());
|
|
15007
|
-
payload.presentation = options.presentation ?? payload.presentation;
|
|
15008
|
-
payload.title = options.title ?? action.title ?? payload.title;
|
|
15009
|
-
payload.subtitle = options.subtitle ?? action.description ?? payload.subtitle;
|
|
15010
|
-
payload.icon = options.icon ?? payload.icon;
|
|
15011
|
-
payload.context = {
|
|
15012
|
-
resource: {
|
|
15013
|
-
resourceKey: action.resourceKey,
|
|
15014
|
-
resourcePath,
|
|
15015
|
-
resourceId: options.resourceId ?? null,
|
|
15016
|
-
group: options.group ?? null,
|
|
15017
|
-
},
|
|
15018
|
-
action: {
|
|
15019
|
-
id: action.id,
|
|
15020
|
-
scope: action.scope,
|
|
15021
|
-
operationId: action.operationId,
|
|
15022
|
-
path: action.path,
|
|
15023
|
-
method: action.method,
|
|
15024
|
-
requestSchemaId: action.requestSchemaId ?? null,
|
|
15025
|
-
requestSchemaUrl: action.requestSchemaUrl,
|
|
15026
|
-
responseSchemaId: action.responseSchemaId ?? null,
|
|
15027
|
-
responseSchemaUrl: action.responseSchemaUrl ?? null,
|
|
15028
|
-
availability: action.availability,
|
|
15029
|
-
successMessage: action.successMessage ?? null,
|
|
15030
|
-
tags: action.tags,
|
|
15031
|
-
execution: action.execution ?? null,
|
|
15032
|
-
},
|
|
15033
|
-
};
|
|
15034
|
-
payload.widget.inputs = {
|
|
15035
|
-
...(payload.widget.inputs || {}),
|
|
15036
|
-
resourcePath,
|
|
15037
|
-
formId: this.buildStableInstanceId(action),
|
|
15038
|
-
mode: 'create',
|
|
15039
|
-
configPersistenceStrategy: 'input-first',
|
|
15040
|
-
layoutPolicy: {
|
|
15041
|
-
source: 'schema',
|
|
15042
|
-
intent: 'command',
|
|
15043
|
-
preset: 'groupedCommand',
|
|
15044
|
-
lifecycle: 'live',
|
|
15045
|
-
persistence: 'transient',
|
|
15046
|
-
schemaType: 'request',
|
|
15047
|
-
},
|
|
15048
|
-
schemaUrl: resolvedRequestSchemaUrl,
|
|
15049
|
-
apiEndpointKey: options.endpointKey ?? null,
|
|
15050
|
-
apiUrlEntry: options.apiUrlEntry ?? (discoveryOptions ? this.discovery.resolveApiEntry(discoveryOptions) : null),
|
|
15051
|
-
submitMethod: String(action.method || '').trim().toLowerCase(),
|
|
15052
|
-
submitUrl: resolvedSubmitUrl,
|
|
15053
|
-
responseSchemaUrl: resolvedResponseSchemaUrl,
|
|
15054
|
-
};
|
|
15055
|
-
if (options.initialValue) {
|
|
15056
|
-
payload.widget.inputs['initialValue'] = this.clone(options.initialValue);
|
|
15057
|
-
}
|
|
15058
|
-
if (action.scope === 'ITEM') {
|
|
15059
|
-
if (options.resourceId != null) {
|
|
15060
|
-
payload.widget.inputs['resourceId'] = options.resourceId;
|
|
15061
|
-
}
|
|
15062
|
-
else if (options.idBindingPath) {
|
|
15063
|
-
payload.bindings = [
|
|
15064
|
-
...(payload.bindings || []),
|
|
15065
|
-
this.buildIdBinding(options.idBindingPath),
|
|
15066
|
-
];
|
|
15067
|
-
}
|
|
15068
|
-
else {
|
|
15069
|
-
throw new Error(`ResourceActionOpenAdapterService requires resourceId or idBindingPath for item action "${action.id}".`);
|
|
15070
|
-
}
|
|
15071
|
-
}
|
|
15072
|
-
this.applyExecutionInputs(payload, action, options);
|
|
15073
|
-
return payload;
|
|
15074
|
-
}
|
|
15075
|
-
resolveDynamicFormPreset() {
|
|
15076
|
-
const preset = SURFACE_OPEN_PRESETS.find((candidate) => candidate.id === 'praxis-dynamic-form');
|
|
15077
|
-
if (!preset) {
|
|
15078
|
-
throw new Error('Missing canonical surface preset "praxis-dynamic-form".');
|
|
15079
|
-
}
|
|
15080
|
-
return preset.payload;
|
|
15081
|
-
}
|
|
15082
|
-
buildStableInstanceId(action) {
|
|
15083
|
-
return `${action.resourceKey}.action.${action.id}`.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
15084
|
-
}
|
|
15085
|
-
applyExecutionInputs(payload, action, options) {
|
|
15086
|
-
const execution = action.execution;
|
|
15087
|
-
if (!execution) {
|
|
15088
|
-
payload.widget.inputs['submitIdempotencyKey'] = this.createCommandIdentity('idempotency');
|
|
15089
|
-
return;
|
|
15090
|
-
}
|
|
15091
|
-
const inputs = payload.widget.inputs;
|
|
15092
|
-
if (execution.preconditions.idempotencyKey !== 'NONE') {
|
|
15093
|
-
inputs['submitIdempotencyKey'] = this.createCommandIdentity('idempotency');
|
|
15094
|
-
}
|
|
15095
|
-
if (execution.preconditions.correlationId !== 'NONE') {
|
|
15096
|
-
inputs['submitCorrelationId'] =
|
|
15097
|
-
String(options.correlationId ?? '').trim() || this.createCommandIdentity('correlation');
|
|
15098
|
-
}
|
|
15099
|
-
if (execution.preconditions.resourceVersionTransport !== 'IF_MATCH') {
|
|
15100
|
-
return;
|
|
15101
|
-
}
|
|
15102
|
-
if (options.resourceVersion != null && String(options.resourceVersion).trim()) {
|
|
15103
|
-
inputs['submitResourceVersion'] = options.resourceVersion;
|
|
15104
|
-
return;
|
|
15105
|
-
}
|
|
15106
|
-
if (options.resourceVersionBindingPath) {
|
|
15107
|
-
payload.bindings = [
|
|
15108
|
-
...(payload.bindings || []),
|
|
15109
|
-
{
|
|
15110
|
-
from: options.resourceVersionBindingPath,
|
|
15111
|
-
to: 'widget.inputs.submitResourceVersion',
|
|
15112
|
-
mode: 'path',
|
|
15113
|
-
},
|
|
15114
|
-
];
|
|
15115
|
-
return;
|
|
15116
|
-
}
|
|
15117
|
-
if (execution.preconditions.resourceVersion === 'REQUIRED') {
|
|
15118
|
-
throw new Error(`ResourceActionOpenAdapterService requires resourceVersion or resourceVersionBindingPath for action "${action.id}".`);
|
|
15119
|
-
}
|
|
15120
|
-
}
|
|
15121
|
-
createIdempotencyKey() {
|
|
15122
|
-
const randomUuid = globalThis.crypto?.randomUUID?.bind(globalThis.crypto);
|
|
15123
|
-
return randomUuid ? randomUuid() : `praxis-action-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
15124
|
-
}
|
|
15125
|
-
createCommandIdentity(kind) {
|
|
15126
|
-
return `${kind}-${this.createIdempotencyKey()}`;
|
|
15127
|
-
}
|
|
15128
|
-
normalizeResourcePath(resourcePath) {
|
|
15129
|
-
return String(resourcePath || '').trim().replace(/^\/+/, '').replace(/\/+$/, '');
|
|
15130
|
-
}
|
|
15131
|
-
buildIdBinding(idBindingPath) {
|
|
15132
|
-
return {
|
|
15133
|
-
from: idBindingPath,
|
|
15134
|
-
to: 'widget.inputs.resourceId',
|
|
15135
|
-
mode: 'path',
|
|
15136
|
-
};
|
|
15137
|
-
}
|
|
15138
|
-
clone(value) {
|
|
15139
|
-
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
15140
|
-
}
|
|
15141
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceActionOpenAdapterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
15142
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceActionOpenAdapterService, providedIn: 'any' });
|
|
15143
|
-
}
|
|
15144
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceActionOpenAdapterService, decorators: [{
|
|
15145
|
-
type: Injectable,
|
|
15146
|
-
args: [{ providedIn: 'any' }]
|
|
15147
|
-
}] });
|
|
15148
|
-
|
|
15149
|
-
class SurfaceOutletRegistryService {
|
|
15150
|
-
registrations = new Map();
|
|
15151
|
-
register(registration) {
|
|
15152
|
-
const key = this.key(registration.resourceKey, registration.surfaceId);
|
|
15153
|
-
this.registrations.set(key, [...(this.registrations.get(key) ?? []), registration]
|
|
15154
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)));
|
|
15155
|
-
return () => {
|
|
15156
|
-
const next = (this.registrations.get(key) ?? []).filter((item) => item !== registration);
|
|
15157
|
-
next.length ? this.registrations.set(key, next) : this.registrations.delete(key);
|
|
15158
|
-
};
|
|
15159
|
-
}
|
|
15160
|
-
async tryActivate(payload, context) {
|
|
15161
|
-
const resourceKey = String(payload.context?.['surface']?.['resourceKey'] ?? '').trim();
|
|
15162
|
-
const surfaceId = String(payload.context?.['surface']?.['id'] ?? '').trim();
|
|
15163
|
-
if (!resourceKey || !surfaceId)
|
|
15323
|
+
if (!observation.lifecycle.active) {
|
|
15164
15324
|
return false;
|
|
15165
|
-
for (const registration of this.registrations.get(this.key(resourceKey, surfaceId)) ?? []) {
|
|
15166
|
-
if (await registration.activate(payload, context))
|
|
15167
|
-
return true;
|
|
15168
15325
|
}
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
|
|
15172
|
-
|
|
15326
|
+
const ttlMs = observation.lifecycle.ttlMs;
|
|
15327
|
+
if (ttlMs === undefined || ttlMs < 0) {
|
|
15328
|
+
return true;
|
|
15329
|
+
}
|
|
15330
|
+
const capturedAt = Date.parse(observation.lifecycle.capturedAt);
|
|
15331
|
+
return Number.isFinite(capturedAt) && Date.now() - capturedAt <= ttlMs;
|
|
15173
15332
|
}
|
|
15174
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type:
|
|
15175
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type:
|
|
15333
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisRuntimeComponentObservationRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
15334
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisRuntimeComponentObservationRegistryService, providedIn: 'root' });
|
|
15176
15335
|
}
|
|
15177
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type:
|
|
15336
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisRuntimeComponentObservationRegistryService, decorators: [{
|
|
15178
15337
|
type: Injectable,
|
|
15179
15338
|
args: [{ providedIn: 'root' }]
|
|
15180
15339
|
}] });
|
|
15340
|
+
function registerPraxisRuntimeComponentObservation(provider, options = {}) {
|
|
15341
|
+
const registry = inject(PraxisRuntimeComponentObservationRegistryService);
|
|
15342
|
+
const destroyRef = options.destroyRef ?? inject(DestroyRef);
|
|
15343
|
+
return registry.register(provider, { ...options, destroyRef });
|
|
15344
|
+
}
|
|
15181
15345
|
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
15188
|
-
'state.idle.description': 'Selecione uma surface relacionada para carregar os dados.',
|
|
15189
|
-
'state.resolving.title': 'Resolvendo recurso relacionado',
|
|
15190
|
-
'state.resolving.description': 'Validando metadados e permissões da relação.',
|
|
15191
|
-
'state.loading.title': 'Carregando recurso relacionado',
|
|
15192
|
-
'state.loading.description': 'Aguarde enquanto a coleção relacionada é carregada.',
|
|
15193
|
-
'state.empty.title': 'Nenhuma operação de leitura publicada',
|
|
15194
|
-
'state.empty.description': 'A relação existe, mas não há operação de lista ou filtro disponível.',
|
|
15195
|
-
'state.permission-limited.title': 'Acesso limitado',
|
|
15196
|
-
'state.permission-limited.description': 'O contexto atual não permite abrir este recurso relacionado.',
|
|
15197
|
-
'state.not-found.title': 'Relação indisponível',
|
|
15198
|
-
'state.not-found.description': 'Não foi possível resolver a relação ou o identificador do registro pai.',
|
|
15199
|
-
'state.error.title': 'Falha ao preparar recurso relacionado',
|
|
15200
|
-
'state.error.description': 'Ocorreu um erro ao preparar a superfície relacionada.',
|
|
15201
|
-
'emptyState.related.title': 'Sem registros em {label}',
|
|
15202
|
-
'emptyState.related.description': 'Esta coleção relacionada não possui registros para o contexto selecionado.',
|
|
15203
|
-
'emptyState.related.descriptionWithAction': 'Use a ação principal para adicionar um registro relacionado quando houver informações para registrar.',
|
|
15204
|
-
'emptyState.related.action.create': 'Adicionar registro',
|
|
15205
|
-
'action.open': 'Abrir relacionado',
|
|
15206
|
-
'status.ready': 'Recurso relacionado pronto',
|
|
15207
|
-
},
|
|
15208
|
-
'en-US': {
|
|
15209
|
-
'state.idle.title': 'Related resource not selected',
|
|
15210
|
-
'state.idle.description': 'Select a related surface to load its data.',
|
|
15211
|
-
'state.resolving.title': 'Resolving related resource',
|
|
15212
|
-
'state.resolving.description': 'Validating relation metadata and permissions.',
|
|
15213
|
-
'state.loading.title': 'Loading related resource',
|
|
15214
|
-
'state.loading.description': 'Wait while the related collection is loaded.',
|
|
15215
|
-
'state.empty.title': 'No read operation published',
|
|
15216
|
-
'state.empty.description': 'The relation exists, but no list or filter operation is available.',
|
|
15217
|
-
'state.permission-limited.title': 'Limited access',
|
|
15218
|
-
'state.permission-limited.description': 'The current context cannot open this related resource.',
|
|
15219
|
-
'state.not-found.title': 'Relation unavailable',
|
|
15220
|
-
'state.not-found.description': 'The relation or parent record identifier could not be resolved.',
|
|
15221
|
-
'state.error.title': 'Failed to prepare related resource',
|
|
15222
|
-
'state.error.description': 'An error occurred while preparing the related surface.',
|
|
15223
|
-
'emptyState.related.title': 'No records in {label}',
|
|
15224
|
-
'emptyState.related.description': 'This related collection has no records for the selected context.',
|
|
15225
|
-
'emptyState.related.descriptionWithAction': 'Use the primary action to add a related record when there is information to capture.',
|
|
15226
|
-
'emptyState.related.action.create': 'Add record',
|
|
15227
|
-
'action.open': 'Open related',
|
|
15228
|
-
'status.ready': 'Related resource ready',
|
|
15229
|
-
},
|
|
15230
|
-
},
|
|
15231
|
-
},
|
|
15232
|
-
};
|
|
15233
|
-
|
|
15234
|
-
class RelatedResourceSurfaceResolverService {
|
|
15235
|
-
i18n = inject(PraxisI18nService, { optional: true });
|
|
15236
|
-
resolve(request) {
|
|
15237
|
-
try {
|
|
15238
|
-
if (!request?.surface) {
|
|
15239
|
-
return { state: 'idle', reason: 'surface-not-provided' };
|
|
15240
|
-
}
|
|
15241
|
-
const surface = request.surface;
|
|
15242
|
-
if (surface.availability?.allowed === false) {
|
|
15243
|
-
return {
|
|
15244
|
-
state: 'permission-limited',
|
|
15245
|
-
reason: surface.availability.reason || 'surface-not-allowed',
|
|
15246
|
-
surface,
|
|
15247
|
-
relatedResource: surface.relatedResource ?? null,
|
|
15248
|
-
};
|
|
15249
|
-
}
|
|
15250
|
-
const relatedResource = surface.relatedResource ?? null;
|
|
15251
|
-
if (!this.isCompleteRelatedResource(relatedResource)) {
|
|
15252
|
-
return {
|
|
15253
|
-
state: 'not-found',
|
|
15254
|
-
reason: 'related-resource-not-published',
|
|
15255
|
-
surface,
|
|
15256
|
-
relatedResource,
|
|
15257
|
-
};
|
|
15258
|
-
}
|
|
15259
|
-
if (!this.hasReadOperation(relatedResource)) {
|
|
15260
|
-
return {
|
|
15261
|
-
state: 'empty',
|
|
15262
|
-
reason: 'related-resource-read-operation-not-published',
|
|
15263
|
-
surface,
|
|
15264
|
-
relatedResource,
|
|
15265
|
-
};
|
|
15266
|
-
}
|
|
15267
|
-
const parentResourceId = this.resolveParentResourceId(request, relatedResource);
|
|
15268
|
-
if (parentResourceId == null || parentResourceId === '') {
|
|
15269
|
-
return {
|
|
15270
|
-
state: 'not-found',
|
|
15271
|
-
reason: 'parent-resource-id-not-resolved',
|
|
15272
|
-
surface,
|
|
15273
|
-
relatedResource,
|
|
15274
|
-
};
|
|
15275
|
-
}
|
|
15276
|
-
const queryContext = this.buildQueryContext(request.queryContext, relatedResource.childParentField, parentResourceId);
|
|
15277
|
-
const payload = this.buildPayload(surface, relatedResource, parentResourceId, queryContext, request);
|
|
15278
|
-
return {
|
|
15279
|
-
state: 'ready',
|
|
15280
|
-
surface,
|
|
15281
|
-
relatedResource,
|
|
15282
|
-
parentResourceId,
|
|
15283
|
-
childResourcePath: relatedResource.childResourcePath,
|
|
15284
|
-
childResourceKey: relatedResource.childResourceKey,
|
|
15285
|
-
queryContext,
|
|
15286
|
-
payload,
|
|
15287
|
-
};
|
|
15288
|
-
}
|
|
15289
|
-
catch (error) {
|
|
15290
|
-
return {
|
|
15291
|
-
state: 'error',
|
|
15292
|
-
reason: error instanceof Error ? error.message : 'related-resource-resolution-failed',
|
|
15293
|
-
surface: request?.surface ?? null,
|
|
15294
|
-
relatedResource: request?.surface?.relatedResource ?? null,
|
|
15295
|
-
};
|
|
15346
|
+
class ResourceActionOpenAdapterService {
|
|
15347
|
+
discovery = inject(ResourceDiscoveryService);
|
|
15348
|
+
toPayload(action, options) {
|
|
15349
|
+
const resourcePath = this.normalizeResourcePath(options.resourcePath);
|
|
15350
|
+
if (!resourcePath) {
|
|
15351
|
+
throw new Error('ResourceActionOpenAdapterService requires resourcePath.');
|
|
15296
15352
|
}
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
return { state, reason };
|
|
15300
|
-
}
|
|
15301
|
-
buildPayload(surface, relatedResource, parentResourceId, queryContext, request) {
|
|
15302
|
-
const preset = SURFACE_OPEN_PRESETS.find((candidate) => candidate.id === 'praxis-table');
|
|
15303
|
-
if (!preset) {
|
|
15304
|
-
throw new Error('Missing canonical surface preset "praxis-table".');
|
|
15353
|
+
if (!action.requestSchemaUrl) {
|
|
15354
|
+
throw new Error(`ResourceActionOpenAdapterService requires requestSchemaUrl for action "${action.id}".`);
|
|
15305
15355
|
}
|
|
15306
|
-
const
|
|
15307
|
-
payload.presentation = request.presentation ?? payload.presentation;
|
|
15308
|
-
payload.title = request.title || surface.title;
|
|
15309
|
-
payload.subtitle = request.subtitle || surface.description || undefined;
|
|
15310
|
-
payload.icon = request.icon || payload.icon;
|
|
15311
|
-
payload.widget.inputs = {
|
|
15312
|
-
...(payload.widget.inputs || {}),
|
|
15313
|
-
configPersistenceStrategy: 'volatile',
|
|
15314
|
-
resourcePath: this.normalizeResourcePath(relatedResource.childResourcePath),
|
|
15315
|
-
apiEndpointKey: request.apiEndpointKey ?? null,
|
|
15316
|
-
apiUrlEntry: request.apiUrlEntry ?? null,
|
|
15317
|
-
tableId: request.tableId || this.buildStableTableId(surface, relatedResource, parentResourceId),
|
|
15318
|
-
config: this.buildTableConfig(surface, relatedResource, request),
|
|
15319
|
-
queryContext,
|
|
15320
|
-
enableCustomization: request.enableCustomization === true,
|
|
15321
|
-
...(this.trim(request.authoringCapability)
|
|
15322
|
-
? { authoringCapability: this.trim(request.authoringCapability) }
|
|
15323
|
-
: {}),
|
|
15324
|
-
};
|
|
15325
|
-
payload.context = {
|
|
15326
|
-
...(payload.context || {}),
|
|
15327
|
-
resource: {
|
|
15328
|
-
resourceKey: surface.resourceKey,
|
|
15329
|
-
resourcePath: this.normalizeResourcePath(request.parentResourcePath || ''),
|
|
15330
|
-
resourceId: parentResourceId,
|
|
15331
|
-
},
|
|
15332
|
-
surface,
|
|
15333
|
-
relatedResource,
|
|
15334
|
-
childResource: {
|
|
15335
|
-
resourceKey: relatedResource.childResourceKey,
|
|
15336
|
-
resourcePath: this.normalizeResourcePath(relatedResource.childResourcePath),
|
|
15337
|
-
parentField: relatedResource.childParentField,
|
|
15338
|
-
selectable: relatedResource.selectable,
|
|
15339
|
-
selectionKeyField: relatedResource.selectionKeyField,
|
|
15340
|
-
operations: relatedResource.childOperations,
|
|
15341
|
-
},
|
|
15342
|
-
};
|
|
15343
|
-
return payload;
|
|
15344
|
-
}
|
|
15345
|
-
buildTableConfig(surface, relatedResource, request) {
|
|
15346
|
-
const tableConfig = request.tableConfig;
|
|
15347
|
-
const emptyState = request.emptyState;
|
|
15348
|
-
const hasTableConfig = !!tableConfig && typeof tableConfig === 'object' && !Array.isArray(tableConfig);
|
|
15349
|
-
const hasEmptyState = !!emptyState && typeof emptyState === 'object' && !Array.isArray(emptyState);
|
|
15350
|
-
const base = hasTableConfig ? tableConfig : {};
|
|
15351
|
-
const toolbar = this.objectValue(base['toolbar']);
|
|
15352
|
-
const columnsVisibility = this.objectValue(toolbar['columnsVisibility']);
|
|
15353
|
-
const behavior = this.objectValue(base['behavior']);
|
|
15354
|
-
const currentEmptyState = this.objectValue(behavior['emptyState']);
|
|
15355
|
-
const hasCurrentEmptyState = Object.keys(currentEmptyState).length > 0;
|
|
15356
|
-
const resolvedEmptyState = hasEmptyState
|
|
15356
|
+
const discoveryOptions = options.endpointKey || options.apiUrlEntry
|
|
15357
15357
|
? {
|
|
15358
|
-
...
|
|
15359
|
-
...
|
|
15358
|
+
...(options.endpointKey ? { endpointKey: options.endpointKey } : {}),
|
|
15359
|
+
...(options.apiUrlEntry ? { apiUrlEntry: options.apiUrlEntry } : {}),
|
|
15360
15360
|
}
|
|
15361
|
-
:
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15361
|
+
: undefined;
|
|
15362
|
+
const resolvedRequestSchemaUrl = this.discovery.resolveHref(action.requestSchemaUrl, discoveryOptions);
|
|
15363
|
+
const resolvedResponseSchemaUrl = action.responseSchemaUrl
|
|
15364
|
+
? this.discovery.resolveHref(action.responseSchemaUrl, discoveryOptions)
|
|
15365
|
+
: null;
|
|
15366
|
+
const resolvedSubmitUrl = this.discovery.resolveHref(action.path, discoveryOptions);
|
|
15367
|
+
const payload = this.clone(this.resolveDynamicFormPreset());
|
|
15368
|
+
payload.presentation = options.presentation ?? payload.presentation;
|
|
15369
|
+
payload.title = options.title ?? action.title ?? payload.title;
|
|
15370
|
+
payload.subtitle = options.subtitle ?? action.description ?? payload.subtitle;
|
|
15371
|
+
payload.icon = options.icon ?? payload.icon;
|
|
15372
|
+
payload.context = {
|
|
15373
|
+
resource: {
|
|
15374
|
+
resourceKey: action.resourceKey,
|
|
15375
|
+
resourcePath,
|
|
15376
|
+
resourceId: options.resourceId ?? null,
|
|
15377
|
+
group: options.group ?? null,
|
|
15375
15378
|
},
|
|
15376
|
-
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
+
action: {
|
|
15380
|
+
id: action.id,
|
|
15381
|
+
scope: action.scope,
|
|
15382
|
+
operationId: action.operationId,
|
|
15383
|
+
path: action.path,
|
|
15384
|
+
method: action.method,
|
|
15385
|
+
requestSchemaId: action.requestSchemaId ?? null,
|
|
15386
|
+
requestSchemaUrl: action.requestSchemaUrl,
|
|
15387
|
+
responseSchemaId: action.responseSchemaId ?? null,
|
|
15388
|
+
responseSchemaUrl: action.responseSchemaUrl ?? null,
|
|
15389
|
+
availability: action.availability,
|
|
15390
|
+
successMessage: action.successMessage ?? null,
|
|
15391
|
+
tags: action.tags,
|
|
15392
|
+
execution: action.execution ?? null,
|
|
15379
15393
|
},
|
|
15380
15394
|
};
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
15393
|
-
|
|
15394
|
-
action: 'create',
|
|
15395
|
-
icon: 'add',
|
|
15396
|
-
primary: true,
|
|
15397
|
-
},
|
|
15398
|
-
]
|
|
15399
|
-
: [];
|
|
15400
|
-
return {
|
|
15401
|
-
title: this.t('emptyState.related.title', 'Sem registros em {label}', { label }),
|
|
15402
|
-
message: this.t(descriptionKey, canCreate
|
|
15403
|
-
? 'Use a ação principal para adicionar um registro relacionado quando houver informações para registrar.'
|
|
15404
|
-
: 'Esta coleção relacionada não possui registros para o contexto selecionado.', { label }),
|
|
15405
|
-
icon: this.trim(request.icon) || 'hub',
|
|
15406
|
-
tone: 'neutral',
|
|
15407
|
-
variant: 'inline',
|
|
15408
|
-
density: 'compact',
|
|
15409
|
-
alignment: 'center',
|
|
15410
|
-
iconContainer: 'soft',
|
|
15411
|
-
actions,
|
|
15412
|
-
};
|
|
15413
|
-
}
|
|
15414
|
-
objectValue(value) {
|
|
15415
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
15416
|
-
? value
|
|
15417
|
-
: {};
|
|
15418
|
-
}
|
|
15419
|
-
buildQueryContext(queryContext, childParentField, parentResourceId) {
|
|
15420
|
-
return {
|
|
15421
|
-
...(queryContext || {}),
|
|
15422
|
-
filters: {
|
|
15423
|
-
...(queryContext?.filters || {}),
|
|
15424
|
-
[childParentField]: parentResourceId,
|
|
15425
|
-
},
|
|
15426
|
-
meta: {
|
|
15427
|
-
...(queryContext?.meta || {}),
|
|
15428
|
-
relatedResource: true,
|
|
15429
|
-
parentFilterField: childParentField,
|
|
15395
|
+
payload.widget.inputs = {
|
|
15396
|
+
...(payload.widget.inputs || {}),
|
|
15397
|
+
resourcePath,
|
|
15398
|
+
formId: this.buildStableInstanceId(action),
|
|
15399
|
+
mode: 'create',
|
|
15400
|
+
configPersistenceStrategy: 'input-first',
|
|
15401
|
+
layoutPolicy: {
|
|
15402
|
+
source: 'schema',
|
|
15403
|
+
intent: 'command',
|
|
15404
|
+
preset: 'groupedCommand',
|
|
15405
|
+
lifecycle: 'live',
|
|
15406
|
+
persistence: 'transient',
|
|
15407
|
+
schemaType: 'request',
|
|
15430
15408
|
},
|
|
15409
|
+
schemaUrl: resolvedRequestSchemaUrl,
|
|
15410
|
+
apiEndpointKey: options.endpointKey ?? null,
|
|
15411
|
+
apiUrlEntry: options.apiUrlEntry ?? (discoveryOptions ? this.discovery.resolveApiEntry(discoveryOptions) : null),
|
|
15412
|
+
submitMethod: String(action.method || '').trim().toLowerCase(),
|
|
15413
|
+
submitUrl: resolvedSubmitUrl,
|
|
15414
|
+
responseSchemaUrl: resolvedResponseSchemaUrl,
|
|
15431
15415
|
};
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
if (request.parentResourceId != null) {
|
|
15435
|
-
return request.parentResourceId;
|
|
15436
|
-
}
|
|
15437
|
-
return this.readPath(request.parentRecord, relatedResource.parentIdPathVariable);
|
|
15438
|
-
}
|
|
15439
|
-
readPath(record, path) {
|
|
15440
|
-
if (!record || !path) {
|
|
15441
|
-
return null;
|
|
15416
|
+
if (options.initialValue) {
|
|
15417
|
+
payload.widget.inputs['initialValue'] = this.clone(options.initialValue);
|
|
15442
15418
|
}
|
|
15443
|
-
|
|
15444
|
-
if (
|
|
15445
|
-
|
|
15419
|
+
if (action.scope === 'ITEM') {
|
|
15420
|
+
if (options.resourceId != null) {
|
|
15421
|
+
payload.widget.inputs['resourceId'] = options.resourceId;
|
|
15446
15422
|
}
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
return !!relatedResource
|
|
15453
|
-
&& !!this.trim(relatedResource.childResourceKey)
|
|
15454
|
-
&& !!this.trim(relatedResource.childResourcePath)
|
|
15455
|
-
&& !!this.trim(relatedResource.childParentField)
|
|
15456
|
-
&& !!this.trim(relatedResource.parentIdPathVariable)
|
|
15457
|
-
&& Array.isArray(relatedResource.childOperations);
|
|
15458
|
-
}
|
|
15459
|
-
hasReadOperation(relatedResource) {
|
|
15460
|
-
return relatedResource.childOperations.includes('LIST')
|
|
15461
|
-
|| relatedResource.childOperations.includes('FILTER');
|
|
15462
|
-
}
|
|
15463
|
-
buildStableTableId(surface, relatedResource, parentResourceId) {
|
|
15464
|
-
return this.sanitizeStableId(`${relatedResource.childResourceKey}.${surface.id}.${String(parentResourceId)}`);
|
|
15465
|
-
}
|
|
15466
|
-
normalizeResourcePath(resourcePath) {
|
|
15467
|
-
let normalized = this.trim(resourcePath);
|
|
15468
|
-
if (/^https?:\/\//i.test(normalized)) {
|
|
15469
|
-
try {
|
|
15470
|
-
normalized = new URL(normalized).pathname;
|
|
15423
|
+
else if (options.idBindingPath) {
|
|
15424
|
+
payload.bindings = [
|
|
15425
|
+
...(payload.bindings || []),
|
|
15426
|
+
this.buildIdBinding(options.idBindingPath),
|
|
15427
|
+
];
|
|
15471
15428
|
}
|
|
15472
|
-
|
|
15473
|
-
|
|
15429
|
+
else {
|
|
15430
|
+
throw new Error(`ResourceActionOpenAdapterService requires resourceId or idBindingPath for item action "${action.id}".`);
|
|
15474
15431
|
}
|
|
15475
15432
|
}
|
|
15476
|
-
|
|
15477
|
-
|
|
15478
|
-
.replace(/^(?:api\/)+/i, '')
|
|
15479
|
-
.replace(/\/+$/, '');
|
|
15433
|
+
this.applyExecutionInputs(payload, action, options);
|
|
15434
|
+
return payload;
|
|
15480
15435
|
}
|
|
15481
|
-
|
|
15482
|
-
|
|
15436
|
+
resolveDynamicFormPreset() {
|
|
15437
|
+
const preset = SURFACE_OPEN_PRESETS.find((candidate) => candidate.id === 'praxis-dynamic-form');
|
|
15438
|
+
if (!preset) {
|
|
15439
|
+
throw new Error('Missing canonical surface preset "praxis-dynamic-form".');
|
|
15440
|
+
}
|
|
15441
|
+
return preset.payload;
|
|
15483
15442
|
}
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
return lastSegment
|
|
15487
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
15488
|
-
.replace(/\s+/g, ' ')
|
|
15489
|
-
.trim();
|
|
15443
|
+
buildStableInstanceId(action) {
|
|
15444
|
+
return `${action.resourceKey}.action.${action.id}`.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
15490
15445
|
}
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15446
|
+
applyExecutionInputs(payload, action, options) {
|
|
15447
|
+
const execution = action.execution;
|
|
15448
|
+
if (!execution) {
|
|
15449
|
+
payload.widget.inputs['submitIdempotencyKey'] = this.createCommandIdentity('idempotency');
|
|
15450
|
+
return;
|
|
15451
|
+
}
|
|
15452
|
+
const inputs = payload.widget.inputs;
|
|
15453
|
+
if (execution.preconditions.idempotencyKey !== 'NONE') {
|
|
15454
|
+
inputs['submitIdempotencyKey'] = this.createCommandIdentity('idempotency');
|
|
15455
|
+
}
|
|
15456
|
+
if (execution.preconditions.correlationId !== 'NONE') {
|
|
15457
|
+
inputs['submitCorrelationId'] =
|
|
15458
|
+
String(options.correlationId ?? '').trim() || this.createCommandIdentity('correlation');
|
|
15459
|
+
}
|
|
15460
|
+
if (execution.preconditions.resourceVersionTransport !== 'IF_MATCH') {
|
|
15461
|
+
return;
|
|
15462
|
+
}
|
|
15463
|
+
if (options.resourceVersion != null && String(options.resourceVersion).trim()) {
|
|
15464
|
+
inputs['submitResourceVersion'] = options.resourceVersion;
|
|
15465
|
+
return;
|
|
15466
|
+
}
|
|
15467
|
+
if (options.resourceVersionBindingPath) {
|
|
15468
|
+
payload.bindings = [
|
|
15469
|
+
...(payload.bindings || []),
|
|
15470
|
+
{
|
|
15471
|
+
from: options.resourceVersionBindingPath,
|
|
15472
|
+
to: 'widget.inputs.submitResourceVersion',
|
|
15473
|
+
mode: 'path',
|
|
15474
|
+
},
|
|
15475
|
+
];
|
|
15476
|
+
return;
|
|
15477
|
+
}
|
|
15478
|
+
if (execution.preconditions.resourceVersion === 'REQUIRED') {
|
|
15479
|
+
throw new Error(`ResourceActionOpenAdapterService requires resourceVersion or resourceVersionBindingPath for action "${action.id}".`);
|
|
15494
15480
|
}
|
|
15495
|
-
return this.interpolate(fallback, params);
|
|
15496
15481
|
}
|
|
15497
|
-
|
|
15498
|
-
|
|
15482
|
+
createIdempotencyKey() {
|
|
15483
|
+
const randomUuid = globalThis.crypto?.randomUUID?.bind(globalThis.crypto);
|
|
15484
|
+
return randomUuid ? randomUuid() : `praxis-action-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
15499
15485
|
}
|
|
15500
|
-
|
|
15501
|
-
return
|
|
15486
|
+
createCommandIdentity(kind) {
|
|
15487
|
+
return `${kind}-${this.createIdempotencyKey()}`;
|
|
15488
|
+
}
|
|
15489
|
+
normalizeResourcePath(resourcePath) {
|
|
15490
|
+
return String(resourcePath || '').trim().replace(/^\/+/, '').replace(/\/+$/, '');
|
|
15491
|
+
}
|
|
15492
|
+
buildIdBinding(idBindingPath) {
|
|
15493
|
+
return {
|
|
15494
|
+
from: idBindingPath,
|
|
15495
|
+
to: 'widget.inputs.resourceId',
|
|
15496
|
+
mode: 'path',
|
|
15497
|
+
};
|
|
15502
15498
|
}
|
|
15503
15499
|
clone(value) {
|
|
15504
15500
|
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
15505
15501
|
}
|
|
15506
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type:
|
|
15507
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type:
|
|
15502
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceActionOpenAdapterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
15503
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceActionOpenAdapterService, providedIn: 'any' });
|
|
15508
15504
|
}
|
|
15509
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type:
|
|
15505
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceActionOpenAdapterService, decorators: [{
|
|
15510
15506
|
type: Injectable,
|
|
15511
15507
|
args: [{ providedIn: 'any' }]
|
|
15512
15508
|
}] });
|
|
15513
15509
|
|
|
15510
|
+
class SurfaceOutletRegistryService {
|
|
15511
|
+
registrations = new Map();
|
|
15512
|
+
register(registration) {
|
|
15513
|
+
const key = this.key(registration.resourceKey, registration.surfaceId);
|
|
15514
|
+
this.registrations.set(key, [...(this.registrations.get(key) ?? []), registration]
|
|
15515
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)));
|
|
15516
|
+
return () => {
|
|
15517
|
+
const next = (this.registrations.get(key) ?? []).filter((item) => item !== registration);
|
|
15518
|
+
next.length ? this.registrations.set(key, next) : this.registrations.delete(key);
|
|
15519
|
+
};
|
|
15520
|
+
}
|
|
15521
|
+
async tryActivate(payload, context) {
|
|
15522
|
+
const resourceKey = String(payload.context?.['surface']?.['resourceKey'] ?? '').trim();
|
|
15523
|
+
const surfaceId = String(payload.context?.['surface']?.['id'] ?? '').trim();
|
|
15524
|
+
if (!resourceKey || !surfaceId)
|
|
15525
|
+
return false;
|
|
15526
|
+
for (const registration of this.registrations.get(this.key(resourceKey, surfaceId)) ?? []) {
|
|
15527
|
+
if (await registration.activate(payload, context))
|
|
15528
|
+
return true;
|
|
15529
|
+
}
|
|
15530
|
+
return false;
|
|
15531
|
+
}
|
|
15532
|
+
key(resourceKey, surfaceId) {
|
|
15533
|
+
return `${resourceKey.trim().toLowerCase()}::${surfaceId.trim().toLowerCase()}`;
|
|
15534
|
+
}
|
|
15535
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SurfaceOutletRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
15536
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SurfaceOutletRegistryService, providedIn: 'root' });
|
|
15537
|
+
}
|
|
15538
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SurfaceOutletRegistryService, decorators: [{
|
|
15539
|
+
type: Injectable,
|
|
15540
|
+
args: [{ providedIn: 'root' }]
|
|
15541
|
+
}] });
|
|
15542
|
+
|
|
15514
15543
|
class SurfaceOpenMaterializerService {
|
|
15515
15544
|
discovery = inject(ResourceDiscoveryService);
|
|
15516
15545
|
async materialize(payload, context) {
|
|
@@ -15784,6 +15813,8 @@ class SurfaceOpenMaterializerService {
|
|
|
15784
15813
|
path: '',
|
|
15785
15814
|
schemaPath: schemaResourcePath || null,
|
|
15786
15815
|
idField: this.resolveRelatedSelectionKeyField(payload),
|
|
15816
|
+
title: payload.title || null,
|
|
15817
|
+
formTitle: payload.title || null,
|
|
15787
15818
|
endpointKey: previousInputs['apiEndpointKey'] ?? undefined,
|
|
15788
15819
|
apiUrlEntry: previousInputs['apiUrlEntry'] ?? undefined,
|
|
15789
15820
|
},
|
|
@@ -15793,12 +15824,6 @@ class SurfaceOpenMaterializerService {
|
|
|
15793
15824
|
queryContext: null,
|
|
15794
15825
|
defaults: {
|
|
15795
15826
|
openMode: 'drawer',
|
|
15796
|
-
modal: {
|
|
15797
|
-
width: 'min(720px, 100vw)',
|
|
15798
|
-
maxWidth: '100vw',
|
|
15799
|
-
height: '100vh',
|
|
15800
|
-
position: 'end',
|
|
15801
|
-
},
|
|
15802
15827
|
},
|
|
15803
15828
|
actions,
|
|
15804
15829
|
},
|
|
@@ -16029,12 +16054,14 @@ class SurfaceOpenMaterializerService {
|
|
|
16029
16054
|
buildRelatedCrudActions(payload, operations, paths) {
|
|
16030
16055
|
const noun = this.resolveRelatedActionNoun(payload);
|
|
16031
16056
|
const formIdPrefix = this.stableSurfaceId(payload);
|
|
16057
|
+
const contextFields = this.buildRelatedContextFields(payload);
|
|
16032
16058
|
const actions = [];
|
|
16033
16059
|
if (operations.has('CREATE')) {
|
|
16034
16060
|
actions.push({
|
|
16035
16061
|
id: 'create',
|
|
16036
16062
|
action: 'create',
|
|
16037
|
-
label: `
|
|
16063
|
+
label: `Adicionar ${noun}`,
|
|
16064
|
+
tooltip: this.resolveRelatedActionDescription(payload, 'create', noun),
|
|
16038
16065
|
formId: `${formIdPrefix}.create`,
|
|
16039
16066
|
icon: 'add',
|
|
16040
16067
|
color: 'primary',
|
|
@@ -16042,7 +16069,7 @@ class SurfaceOpenMaterializerService {
|
|
|
16042
16069
|
position: 'end',
|
|
16043
16070
|
target: { scope: 'collection' },
|
|
16044
16071
|
openMode: 'drawer',
|
|
16045
|
-
form: this.buildExplicitRelatedActionForm('create', paths),
|
|
16072
|
+
form: this.buildExplicitRelatedActionForm('create', paths, this.buildRelatedParentInitialValue(payload), contextFields),
|
|
16046
16073
|
});
|
|
16047
16074
|
}
|
|
16048
16075
|
if (operations.has('UPDATE')) {
|
|
@@ -16050,6 +16077,7 @@ class SurfaceOpenMaterializerService {
|
|
|
16050
16077
|
id: 'edit',
|
|
16051
16078
|
action: 'edit',
|
|
16052
16079
|
label: `Editar ${noun}`,
|
|
16080
|
+
tooltip: this.resolveRelatedActionDescription(payload, 'edit', noun),
|
|
16053
16081
|
formId: `${formIdPrefix}.edit`,
|
|
16054
16082
|
icon: 'edit',
|
|
16055
16083
|
color: 'primary',
|
|
@@ -16060,14 +16088,15 @@ class SurfaceOpenMaterializerService {
|
|
|
16060
16088
|
cardinality: { min: 1, max: 1 },
|
|
16061
16089
|
},
|
|
16062
16090
|
openMode: 'drawer',
|
|
16063
|
-
form: this.buildExplicitRelatedActionForm('edit', paths),
|
|
16091
|
+
form: this.buildExplicitRelatedActionForm('edit', paths, undefined, contextFields),
|
|
16064
16092
|
});
|
|
16065
16093
|
}
|
|
16066
16094
|
if (operations.has('DELETE')) {
|
|
16067
16095
|
actions.push({
|
|
16068
16096
|
id: 'delete',
|
|
16069
16097
|
action: 'delete',
|
|
16070
|
-
label: `
|
|
16098
|
+
label: `Remover ${noun}`,
|
|
16099
|
+
tooltip: this.resolveRelatedActionDescription(payload, 'delete', noun),
|
|
16071
16100
|
formId: `${formIdPrefix}.delete`,
|
|
16072
16101
|
icon: 'delete',
|
|
16073
16102
|
color: 'warn',
|
|
@@ -16109,7 +16138,7 @@ class SurfaceOpenMaterializerService {
|
|
|
16109
16138
|
},
|
|
16110
16139
|
};
|
|
16111
16140
|
}
|
|
16112
|
-
buildExplicitRelatedActionForm(action, paths) {
|
|
16141
|
+
buildExplicitRelatedActionForm(action, paths, initialValue, contextFields) {
|
|
16113
16142
|
const resourcePath = this.normalizeResourcePath(paths?.resourcePath || '');
|
|
16114
16143
|
const schemaResourcePath = this.normalizeResourcePath(paths?.schemaResourcePath || resourcePath);
|
|
16115
16144
|
if (!resourcePath) {
|
|
@@ -16120,7 +16149,10 @@ class SurfaceOpenMaterializerService {
|
|
|
16120
16149
|
schemaUrl: this.buildSchemaUrl(schemaResourcePath, 'post', 'request'),
|
|
16121
16150
|
submitUrl: resourcePath,
|
|
16122
16151
|
submitMethod: 'post',
|
|
16123
|
-
|
|
16152
|
+
...(initialValue && Object.keys(initialValue).length
|
|
16153
|
+
? { initialValue }
|
|
16154
|
+
: {}),
|
|
16155
|
+
layoutPolicy: this.buildRelatedCommandLayoutPolicy('create', contextFields),
|
|
16124
16156
|
};
|
|
16125
16157
|
}
|
|
16126
16158
|
const itemSchemaPath = schemaResourcePath ? `${schemaResourcePath}/{id}` : '';
|
|
@@ -16130,7 +16162,7 @@ class SurfaceOpenMaterializerService {
|
|
|
16130
16162
|
schemaUrl: this.buildSchemaUrl(itemSchemaPath, 'put', 'request'),
|
|
16131
16163
|
submitUrl: itemSubmitUrl,
|
|
16132
16164
|
submitMethod: 'put',
|
|
16133
|
-
layoutPolicy: this.buildRelatedCommandLayoutPolicy('update'),
|
|
16165
|
+
layoutPolicy: this.buildRelatedCommandLayoutPolicy('update', contextFields),
|
|
16134
16166
|
};
|
|
16135
16167
|
}
|
|
16136
16168
|
return {
|
|
@@ -16138,7 +16170,7 @@ class SurfaceOpenMaterializerService {
|
|
|
16138
16170
|
submitMethod: 'delete',
|
|
16139
16171
|
};
|
|
16140
16172
|
}
|
|
16141
|
-
buildRelatedCommandLayoutPolicy(schemaOperation) {
|
|
16173
|
+
buildRelatedCommandLayoutPolicy(schemaOperation, contextFields) {
|
|
16142
16174
|
return {
|
|
16143
16175
|
source: 'schema',
|
|
16144
16176
|
intent: 'command',
|
|
@@ -16147,6 +16179,9 @@ class SurfaceOpenMaterializerService {
|
|
|
16147
16179
|
persistence: 'transient',
|
|
16148
16180
|
schemaOperation,
|
|
16149
16181
|
schemaType: 'request',
|
|
16182
|
+
...(contextFields?.length
|
|
16183
|
+
? { groupedCommand: { contextFields } }
|
|
16184
|
+
: {}),
|
|
16150
16185
|
};
|
|
16151
16186
|
}
|
|
16152
16187
|
buildSchemaUrl(path, operation, schemaType) {
|
|
@@ -16178,6 +16213,21 @@ class SurfaceOpenMaterializerService {
|
|
|
16178
16213
|
const relatedResource = payload.context?.['relatedResource'];
|
|
16179
16214
|
return relatedResource;
|
|
16180
16215
|
}
|
|
16216
|
+
buildRelatedParentInitialValue(payload) {
|
|
16217
|
+
const parentField = String(this.resolveRelatedResource(payload)?.childParentField || '').trim();
|
|
16218
|
+
const resource = payload.context?.['resource'];
|
|
16219
|
+
const parentResourceId = resource?.['resourceId'];
|
|
16220
|
+
if (!parentField ||
|
|
16221
|
+
parentResourceId == null ||
|
|
16222
|
+
String(parentResourceId).trim() === '') {
|
|
16223
|
+
return undefined;
|
|
16224
|
+
}
|
|
16225
|
+
return { [parentField]: parentResourceId };
|
|
16226
|
+
}
|
|
16227
|
+
buildRelatedContextFields(payload) {
|
|
16228
|
+
const parentField = String(this.resolveRelatedResource(payload)?.childParentField || '').trim();
|
|
16229
|
+
return parentField ? [parentField] : [];
|
|
16230
|
+
}
|
|
16181
16231
|
resolveRelatedSelectionKeyField(payload) {
|
|
16182
16232
|
const relatedResource = this.resolveRelatedResource(payload);
|
|
16183
16233
|
const field = String(relatedResource?.selectionKeyField || '').trim();
|
|
@@ -16214,22 +16264,42 @@ class SurfaceOpenMaterializerService {
|
|
|
16214
16264
|
]
|
|
16215
16265
|
.map((value) => String(value || '').trim())
|
|
16216
16266
|
.find(Boolean) || '';
|
|
16217
|
-
const
|
|
16218
|
-
.
|
|
16219
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
16220
|
-
.toLowerCase();
|
|
16221
|
-
if (/\b(documentos?|documents?)\b/.test(normalized)) {
|
|
16222
|
-
return 'documento';
|
|
16223
|
-
}
|
|
16224
|
-
const firstToken = normalized
|
|
16225
|
-
.split(/[^a-z0-9]+/)
|
|
16267
|
+
const firstToken = source
|
|
16268
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
16226
16269
|
.filter(Boolean)
|
|
16227
|
-
.find((token) => !['de', 'da', 'do', 'das', 'dos'
|
|
16228
|
-
if (firstToken && firstToken.length >
|
|
16229
|
-
return
|
|
16270
|
+
.find((token) => !['de', 'da', 'do', 'das', 'dos'].includes(token.toLocaleLowerCase('pt-BR')));
|
|
16271
|
+
if (firstToken && firstToken.length > 2) {
|
|
16272
|
+
return this.singularizePtBrWord(firstToken).toLocaleLowerCase('pt-BR');
|
|
16230
16273
|
}
|
|
16231
16274
|
return 'item';
|
|
16232
16275
|
}
|
|
16276
|
+
singularizePtBrWord(word) {
|
|
16277
|
+
if (/ções$/iu.test(word))
|
|
16278
|
+
return word.replace(/ções$/iu, 'ção');
|
|
16279
|
+
if (/ões$/iu.test(word))
|
|
16280
|
+
return word.replace(/ões$/iu, 'ão');
|
|
16281
|
+
if (/ais$/iu.test(word))
|
|
16282
|
+
return word.replace(/ais$/iu, 'al');
|
|
16283
|
+
if (/eis$/iu.test(word))
|
|
16284
|
+
return word.replace(/eis$/iu, 'el');
|
|
16285
|
+
if (/res$/iu.test(word))
|
|
16286
|
+
return word.replace(/es$/iu, '');
|
|
16287
|
+
if (/s$/iu.test(word) && !/ss$/iu.test(word))
|
|
16288
|
+
return word.replace(/s$/iu, '');
|
|
16289
|
+
return word;
|
|
16290
|
+
}
|
|
16291
|
+
resolveRelatedActionDescription(payload, action, noun) {
|
|
16292
|
+
const surfaceDescription = String(payload.subtitle
|
|
16293
|
+
|| payload.context?.['surface']?.['description']
|
|
16294
|
+
|| '').trim();
|
|
16295
|
+
if (surfaceDescription)
|
|
16296
|
+
return surfaceDescription;
|
|
16297
|
+
if (action === 'create')
|
|
16298
|
+
return `Adicione ${noun} ao contexto selecionado.`;
|
|
16299
|
+
if (action === 'edit')
|
|
16300
|
+
return `Atualize os dados de ${noun} no contexto selecionado.`;
|
|
16301
|
+
return `Remova ${noun} do contexto selecionado.`;
|
|
16302
|
+
}
|
|
16233
16303
|
inferColumnsFromData(data) {
|
|
16234
16304
|
const first = data.find((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
16235
16305
|
if (!first || typeof first !== 'object')
|
|
@@ -23415,6 +23485,7 @@ function normalizeLayoutPolicy(policy) {
|
|
|
23415
23485
|
? {
|
|
23416
23486
|
orphanFieldExpansion: policy?.groupedCommand?.orphanFieldExpansion ??
|
|
23417
23487
|
'medium-and-wide',
|
|
23488
|
+
contextFields: normalizeFieldNameList(policy?.groupedCommand?.contextFields),
|
|
23418
23489
|
}
|
|
23419
23490
|
: policy?.groupedCommand,
|
|
23420
23491
|
};
|
|
@@ -23692,6 +23763,12 @@ function spanForColumns(columns) {
|
|
|
23692
23763
|
return Math.max(1, Math.min(12, Math.floor(12 / columns)));
|
|
23693
23764
|
}
|
|
23694
23765
|
function fieldsForPolicy(fields, policy) {
|
|
23766
|
+
if (policy.preset === 'groupedCommand') {
|
|
23767
|
+
const contextFields = groupedCommandContextFields(policy);
|
|
23768
|
+
return fields.map((field) => contextFields.has(field.name)
|
|
23769
|
+
? { ...field, formHidden: true, hidden: true }
|
|
23770
|
+
: field);
|
|
23771
|
+
}
|
|
23695
23772
|
if (policy.preset !== 'compactPresentation') {
|
|
23696
23773
|
return fields;
|
|
23697
23774
|
}
|
|
@@ -23710,11 +23787,23 @@ function fieldsForPolicy(fields, policy) {
|
|
|
23710
23787
|
}));
|
|
23711
23788
|
}
|
|
23712
23789
|
function fieldsForLayoutPolicy(fields, policy) {
|
|
23790
|
+
if (policy.preset === 'groupedCommand') {
|
|
23791
|
+
const contextFields = groupedCommandContextFields(policy);
|
|
23792
|
+
return fields.filter((field) => !contextFields.has(field.name));
|
|
23793
|
+
}
|
|
23713
23794
|
if (policy.preset !== 'compactPresentation') {
|
|
23714
23795
|
return fields;
|
|
23715
23796
|
}
|
|
23716
23797
|
return resolveDetailSummaryFields(fields, policy);
|
|
23717
23798
|
}
|
|
23799
|
+
function groupedCommandContextFields(policy) {
|
|
23800
|
+
return new Set(normalizeFieldNameList(policy.groupedCommand?.contextFields));
|
|
23801
|
+
}
|
|
23802
|
+
function normalizeFieldNameList(value) {
|
|
23803
|
+
return Array.from(new Set((value || [])
|
|
23804
|
+
.map((fieldName) => String(fieldName || '').trim())
|
|
23805
|
+
.filter(Boolean)));
|
|
23806
|
+
}
|
|
23718
23807
|
function resolvePresentationFields(fields) {
|
|
23719
23808
|
return fields.filter((field) => !isFormHidden(field));
|
|
23720
23809
|
}
|