@acorex/platform 20.8.18 → 20.8.21

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.
Files changed (27) hide show
  1. package/common/index.d.ts +71 -5
  2. package/fesm2022/acorex-platform-common.mjs +248 -59
  3. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  4. package/fesm2022/acorex-platform-layout-components.mjs +28 -18
  5. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  6. package/fesm2022/acorex-platform-layout-entity.mjs +131 -35
  7. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-layout-views.mjs +7 -3
  9. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  10. package/fesm2022/acorex-platform-layout-widget-core.mjs +87 -16
  11. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-widgets.mjs +1 -1
  13. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  14. package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-D2CtNrSn.mjs → acorex-platform-themes-default-entity-master-list-view.component-DnFEQS-L.mjs} +48 -15
  15. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DnFEQS-L.mjs.map +1 -0
  16. package/fesm2022/acorex-platform-themes-default-error-404.component-CgVwJxdC.mjs +42 -0
  17. package/fesm2022/acorex-platform-themes-default-error-404.component-CgVwJxdC.mjs.map +1 -0
  18. package/fesm2022/acorex-platform-themes-default.mjs +17 -7
  19. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  20. package/layout/components/index.d.ts +2 -1
  21. package/layout/entity/index.d.ts +22 -3
  22. package/layout/views/index.d.ts +2 -1
  23. package/layout/widget-core/index.d.ts +12 -3
  24. package/package.json +8 -8
  25. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-D2CtNrSn.mjs.map +0 -1
  26. package/fesm2022/acorex-platform-themes-default-error-404.component-DVF9soT5.mjs +0 -25
  27. package/fesm2022/acorex-platform-themes-default-error-404.component-DVF9soT5.mjs.map +0 -1
@@ -2,14 +2,14 @@ import { AXToastService } from '@acorex/components/toast';
2
2
  import * as i6 from '@acorex/core/translation';
3
3
  import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
4
4
  import * as i4$1 from '@acorex/platform/common';
5
- import { AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, AXPEntityQueryType, AXPCleanNestedFilters, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER } from '@acorex/platform/common';
5
+ import { AXPNotFoundError, AXPSettingsService, AXPCommonSettings, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, axpRedirectToNotFound, axpIsEntityRecordNotFound, AXPEntityQueryType, AXPCleanNestedFilters, AXPWorkflowNavigateAction, AXP_NOT_FOUND_ROUTE, AXP_PROTECTED_ROUTE_GUARDS, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER } from '@acorex/platform/common';
6
6
  import * as i0 from '@angular/core';
7
7
  import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, computed, ChangeDetectorRef, effect, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, NgModule } from '@angular/core';
8
8
  import { Subject, takeUntil } from 'rxjs';
9
9
  import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
10
10
  import { AXPDeviceService, AXPBroadcastEventService, applyFilterArray, applySortArray, resolveActionLook, AXPExpressionEvaluatorService, AXPDistributedEventListenerService, AXPPlatformScope, AXPColumnWidthService, AXHighlightService, extractValue, setSmart, getChangedPaths, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXPSystemActionType } from '@acorex/platform/core';
11
11
  import { merge, castArray, cloneDeep, get, set, orderBy, isNil, isEmpty, isEqual } from 'lodash-es';
12
- import { AXPSessionService, AXPAuthGuard } from '@acorex/platform/auth';
12
+ import { AXPSessionService } from '@acorex/platform/auth';
13
13
  import { Router, ActivatedRoute, RouterModule, ROUTES } from '@angular/router';
14
14
  import * as i3 from '@acorex/components/button';
15
15
  import { AXButtonModule } from '@acorex/components/button';
@@ -604,7 +604,7 @@ class AXPEntityDefinitionRegistryService {
604
604
  throw error; // Rethrow to allow error handling by caller
605
605
  }
606
606
  if (!config) {
607
- throw new Error(`Invalid entity name: ${key}`);
607
+ throw new AXPNotFoundError(`Invalid entity name: ${key}`);
608
608
  }
609
609
  }
610
610
  return config;
@@ -3948,7 +3948,7 @@ class AXPEntityMasterListViewModel {
3948
3948
  this.events$.next({ action: 'refresh' });
3949
3949
  }
3950
3950
  });
3951
- this.sortedFields.set(this.sortableFields());
3951
+ this.sortedFields.set([]);
3952
3952
  void this.syncShowRowIndexColumnSetting();
3953
3953
  this.settings.onLoaded.pipe(takeUntil(this.destroyed)).subscribe(() => {
3954
3954
  void this.syncShowRowIndexColumnSetting();
@@ -4051,10 +4051,8 @@ class AXPEntityMasterListViewModel {
4051
4051
  .sort((a, b) => columns.findIndex((col) => col.name === (a.column?.options?.dataPath ?? a.name)) -
4052
4052
  columns.findIndex((col) => col.name === (b.column?.options?.dataPath ?? b.name))));
4053
4053
  }
4054
- if (Array.isArray(sorts)) {
4055
- // sorts are AXPSortQuery[]; ensure we map by name
4056
- const sortsMap = new Map(sorts.map((s) => [s.name, s.dir]));
4057
- this.sortedFields.update((prev) => prev.map((sf) => ({ ...sf, dir: sortsMap.get(sf.name) || sf.dir })));
4054
+ if (Array.isArray(sorts) && sorts.length) {
4055
+ this.setActiveSortsFromQueries(sorts);
4058
4056
  }
4059
4057
  // Don't override filters if they came from queryParams
4060
4058
  if (Array.isArray(filters) && !this.hasQueryParamsFilters) {
@@ -4569,18 +4567,80 @@ class AXPEntityMasterListViewModel {
4569
4567
  resetSorts() {
4570
4568
  this.applyViewSorts();
4571
4569
  }
4570
+ /**
4571
+ * Applies active sorts in the given order (click order, saved settings, or view defaults).
4572
+ */
4573
+ setActiveSortsFromQueries(sorts) {
4574
+ const sortableByName = new Map(this.sortableFields().map((f) => [f.name, f]));
4575
+ this.sortedFields.set(sorts
4576
+ .filter((s) => s.dir && sortableByName.has(s.name))
4577
+ .map((s) => ({
4578
+ name: s.name,
4579
+ title: sortableByName.get(s.name).title,
4580
+ dir: s.dir,
4581
+ })));
4582
+ }
4572
4583
  applyViewSorts() {
4573
- const viewSorts = this.view().sorts;
4574
- this.sortedFields.update((prev) => {
4575
- const viewSortsMap = new Map(viewSorts.map((vs) => [vs.name, vs]));
4576
- return prev.map((sf) => {
4577
- const updatedSort = viewSortsMap.get(sf.name);
4578
- if (updatedSort) {
4579
- return { ...updatedSort, title: sf.title };
4580
- }
4581
- return sf;
4582
- });
4583
- });
4584
+ const viewSorts = this.view().sorts ?? [];
4585
+ this.setActiveSortsFromQueries(viewSorts);
4586
+ }
4587
+ /**
4588
+ * Active sort direction for a column (synced with toolbar sort UI and dataSource).
4589
+ */
4590
+ getColumnSortDirection(columnName) {
4591
+ const dir = this.sortedFields().find((sf) => sf.name === columnName)?.dir;
4592
+ return dir === 'asc' || dir === 'desc' ? dir : undefined;
4593
+ }
4594
+ /**
4595
+ * 1-based priority index when multiple columns are sorted (Ctrl+click order).
4596
+ */
4597
+ getColumnSortIndex(columnName) {
4598
+ const index = this.sortedFields().findIndex((sf) => sf.name === columnName);
4599
+ return index >= 0 ? index + 1 : undefined;
4600
+ }
4601
+ /** True when active sorts still match the current view defaults (not yet customized). */
4602
+ isViewDefaultSorts(activeSorts) {
4603
+ const viewSorts = this.view().sorts ?? [];
4604
+ if (activeSorts.length !== viewSorts.length) {
4605
+ return false;
4606
+ }
4607
+ return activeSorts.every((s, i) => s.name === viewSorts[i]?.name && s.dir === viewSorts[i]?.dir);
4608
+ }
4609
+ /**
4610
+ * Toggles column sort (asc → desc → none). Without Ctrl, only one column is active.
4611
+ * With Ctrl/Cmd, multiple columns are sorted in click order (first clicked = primary).
4612
+ */
4613
+ async toggleColumnSort(columnName, multiSort) {
4614
+ const fieldMeta = this.sortableFields().find((f) => f.name === columnName);
4615
+ if (!fieldMeta) {
4616
+ return;
4617
+ }
4618
+ let activeSorts = [...this.sortedFields()];
4619
+ const index = activeSorts.findIndex((sf) => sf.name === columnName);
4620
+ const currentDir = index >= 0 ? activeSorts[index].dir : undefined;
4621
+ const newDir = currentDir === 'asc' ? 'desc' : currentDir === 'desc' ? undefined : 'asc';
4622
+ if (!multiSort) {
4623
+ activeSorts = newDir ? [{ name: columnName, title: fieldMeta.title, dir: newDir }] : [];
4624
+ }
4625
+ else if (index >= 0) {
4626
+ const [item] = activeSorts.splice(index, 1);
4627
+ if (newDir) {
4628
+ // Re-click moves column to end so click order can override view/default order.
4629
+ activeSorts.push({ ...item, title: fieldMeta.title, dir: newDir });
4630
+ }
4631
+ }
4632
+ else if (newDir) {
4633
+ if (this.isViewDefaultSorts(activeSorts)) {
4634
+ activeSorts = [{ name: columnName, title: fieldMeta.title, dir: newDir }];
4635
+ }
4636
+ else {
4637
+ activeSorts.push({ name: columnName, title: fieldMeta.title, dir: newDir });
4638
+ }
4639
+ }
4640
+ this.sortedFields.set(activeSorts);
4641
+ this.lastAppliedSortKey = null;
4642
+ await this.saveSettings('sorts', activeSorts.map((s) => ({ name: s.name, dir: s.dir })));
4643
+ await this.applyFilterAndSort();
4584
4644
  }
4585
4645
  //****************** Commands ******************//
4586
4646
  async executeCommand(commandName, data = null) {
@@ -4718,20 +4778,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
4718
4778
  type: Injectable,
4719
4779
  args: [{ providedIn: 'root' }]
4720
4780
  }] });
4721
- const AXPEntityListViewModelResolver = async (route, state, service = inject(AXPEntityListViewModelFactory)) => {
4781
+ const AXPEntityListViewModelResolver = async (route, state, service = inject(AXPEntityListViewModelFactory), router = inject(Router)) => {
4722
4782
  const moduleName = route.parent?.paramMap.get('module');
4723
4783
  const entityName = route.paramMap.get('entity');
4724
- const vm = await service.create(moduleName, entityName);
4725
- // Check if filters are provided in query params
4726
- const filtersParam = route.queryParamMap.get('filters');
4727
- if (filtersParam) {
4728
- const applied = vm.applyFiltersFromQueryParams(filtersParam);
4729
- if (applied) {
4730
- // Trigger filter and sort application
4731
- await vm.applyFilterAndSort();
4784
+ try {
4785
+ const vm = await service.create(moduleName, entityName);
4786
+ // Check if filters are provided in query params
4787
+ const filtersParam = route.queryParamMap.get('filters');
4788
+ if (filtersParam) {
4789
+ const applied = vm.applyFiltersFromQueryParams(filtersParam);
4790
+ if (applied) {
4791
+ // Trigger filter and sort application
4792
+ await vm.applyFilterAndSort();
4793
+ }
4732
4794
  }
4795
+ return vm;
4796
+ }
4797
+ catch (error) {
4798
+ if (error instanceof AXPNotFoundError) {
4799
+ return axpRedirectToNotFound(router);
4800
+ }
4801
+ throw error;
4733
4802
  }
4734
- return vm;
4735
4803
  };
4736
4804
 
4737
4805
  const AXPEntityDeletedEvent = createWorkFlowEvent('[Entity] Deleted');
@@ -5523,10 +5591,19 @@ class AXPEntityPreloadFiltersViewModel {
5523
5591
  const AXPEntityPreloadFiltersViewModelResolver = async (route, state) => {
5524
5592
  const injector = inject(Injector);
5525
5593
  const entityRegistry = inject(AXPEntityDefinitionRegistryService);
5594
+ const router = inject(Router);
5526
5595
  const moduleName = route.parent?.paramMap.get('module');
5527
5596
  const entityName = route.paramMap.get('entity');
5528
- const entity = await entityRegistry.resolve(moduleName, entityName);
5529
- return new AXPEntityPreloadFiltersViewModel(injector, entity);
5597
+ try {
5598
+ const entity = await entityRegistry.resolve(moduleName, entityName);
5599
+ return new AXPEntityPreloadFiltersViewModel(injector, entity);
5600
+ }
5601
+ catch (error) {
5602
+ if (error instanceof AXPNotFoundError) {
5603
+ return axpRedirectToNotFound(router);
5604
+ }
5605
+ throw error;
5606
+ }
5530
5607
  };
5531
5608
  //#endregion
5532
5609
 
@@ -7101,9 +7178,12 @@ class AXPLayoutAdapterFactory {
7101
7178
  async createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies) {
7102
7179
  const entity = await entityResolver.resolve(moduleName, entityName);
7103
7180
  if (!entity) {
7104
- throw new Error(`Entity ${moduleName}.${entityName} not found`);
7181
+ throw new AXPNotFoundError(`Entity ${moduleName}.${entityName} not found`);
7105
7182
  }
7106
7183
  const rootContext = await this.loadRootContext(entity, id);
7184
+ if (axpIsEntityRecordNotFound(rootContext)) {
7185
+ throw new AXPNotFoundError(`Entity record ${moduleName}.${entityName}#${id} not found`);
7186
+ }
7107
7187
  // Evaluate hidden expressions for related entities once, reuse across building and composing
7108
7188
  const evaluatedRelatedEntities = await this.evaluateRelatedEntitiesHidden(entity?.relatedEntities ?? [], rootContext, dependencies);
7109
7189
  // Build main and related pages using evaluated related entities
@@ -7274,7 +7354,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
7274
7354
  }]
7275
7355
  }], ctorParameters: () => [{ type: AXPRelatedEntityConverterFactory }, { type: AXPMainEntityContentBuilder }, { type: AXPLayoutAdapterBuilder }, { type: i4$1.AXPFilterOperatorMiddlewareService }] });
7276
7356
 
7277
- const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver = inject(AXPEntityDefinitionRegistryService), expressionEvaluator = inject(AXPExpressionEvaluatorService), session = inject(AXPSessionService), formatService = inject(AXFormatService), workflowService = inject(AXPWorkflowService), commandService = inject(AXPCommandService), layoutAdapterFactory = inject(AXPLayoutAdapterFactory)) => {
7357
+ const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver = inject(AXPEntityDefinitionRegistryService), expressionEvaluator = inject(AXPExpressionEvaluatorService), session = inject(AXPSessionService), formatService = inject(AXFormatService), workflowService = inject(AXPWorkflowService), commandService = inject(AXPCommandService), layoutAdapterFactory = inject(AXPLayoutAdapterFactory), router = inject(Router)) => {
7278
7358
  const moduleName = route.parent?.paramMap.get('module');
7279
7359
  const entityName = route.paramMap.get('entity');
7280
7360
  const id = route.paramMap.get('id');
@@ -7286,7 +7366,15 @@ const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver =
7286
7366
  workflowService,
7287
7367
  commandService,
7288
7368
  };
7289
- return await layoutAdapterFactory.createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies);
7369
+ try {
7370
+ return await layoutAdapterFactory.createDetailsViewAdapter(entityResolver, moduleName, entityName, id, dependencies);
7371
+ }
7372
+ catch (error) {
7373
+ if (error instanceof AXPNotFoundError) {
7374
+ return axpRedirectToNotFound(router);
7375
+ }
7376
+ throw error;
7377
+ }
7290
7378
  };
7291
7379
 
7292
7380
  class AXPEntityPreloadFilterGuard {
@@ -16155,7 +16243,7 @@ function routesFacory() {
16155
16243
  loadComponent: () => {
16156
16244
  return config.viewers.root();
16157
16245
  },
16158
- canActivate: [AXPAuthGuard],
16246
+ canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
16159
16247
  children: [
16160
16248
  {
16161
16249
  path: ':module',
@@ -16211,8 +16299,16 @@ function routesFacory() {
16211
16299
  redirectTo: ':entity/list',
16212
16300
  pathMatch: 'full',
16213
16301
  },
16302
+ {
16303
+ path: '**',
16304
+ redirectTo: AXP_NOT_FOUND_ROUTE,
16305
+ },
16214
16306
  ],
16215
16307
  },
16308
+ {
16309
+ path: '**',
16310
+ redirectTo: AXP_NOT_FOUND_ROUTE,
16311
+ },
16216
16312
  ],
16217
16313
  },
16218
16314
  ];