@elasticias/screens 1.0.48 → 1.0.49

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.
@@ -1,13 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Input, Component, signal, InjectionToken, inject, ChangeDetectorRef, ViewChild, Injector, computed, DestroyRef } from '@angular/core';
2
+ import { Input, Component, signal, InjectionToken, inject, ChangeDetectorRef, ViewChild, Injector, DestroyRef, computed } from '@angular/core';
3
3
  import { PersistedState, StorageUtils, AppUtils, CsvUtils } from '@elasticias/utils';
4
- import { Subject, combineLatest, take, merge, map, distinctUntilChanged } from 'rxjs';
4
+ import { Subject, combineLatest, take, from, merge, map, distinctUntilChanged } from 'rxjs';
5
5
  import { Permissions } from '@elasticias/types';
6
6
  import { Router, ActivatedRoute } from '@angular/router';
7
7
  import { CacheService, ToastService, ConfirmDialogService, EfShortcutService } from '@elasticias/core';
8
8
  import { Location } from '@angular/common';
9
- import { TranslateService } from '@ngx-translate/core';
10
9
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
10
+ import { TranslateService } from '@ngx-translate/core';
11
11
 
12
12
  class AbstractEntity {
13
13
  clone(entity) {
@@ -1054,6 +1054,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
1054
1054
  class AbstractSearchScreenV2 extends AbstractScreenComponent {
1055
1055
  screenState = ScreenStateEnum.SEARCH;
1056
1056
  injector = inject(Injector);
1057
+ destroyRef = inject(DestroyRef);
1058
+ /**
1059
+ * A delete confirm is loading or open. A second click (or the row
1060
+ * shortcut) meanwhile is ignored, so one gesture cannot start two impact
1061
+ * reads or stack two confirms. Cleared when the confirm is answered
1062
+ * (PrimeNG calls reject on close and Escape too) or when the read
1063
+ * yields no confirm.
1064
+ */
1065
+ deleteConfirmBusy = false;
1057
1066
  serviceInstance;
1058
1067
  /**
1059
1068
  * Translate lazily, through the injector.
@@ -1818,29 +1827,93 @@ class AbstractSearchScreenV2 extends AbstractScreenComponent {
1818
1827
  getDeleteConfirmName(_id) {
1819
1828
  return undefined;
1820
1829
  }
1830
+ /**
1831
+ * Async details for the delete confirmation, read before it opens. Use
1832
+ * it when the confirm has to say what the delete will take with it, such
1833
+ * as how many products lose a collection:
1834
+ *
1835
+ * ```ts
1836
+ * protected override getDeleteConfirmOptions(id: any) {
1837
+ * return this.collectionsClient.deleteImpact(id).pipe(
1838
+ * map(impact => ({
1839
+ * messageKey: impact.onHome ? 'x_delete_message_home' : 'x_delete_message',
1840
+ * messageParams: { count: impact.productCount ?? 0 },
1841
+ * })),
1842
+ * );
1843
+ * }
1844
+ * ```
1845
+ *
1846
+ * The returned options are merged over `{ intent: 'delete', name:
1847
+ * getDeleteConfirmName(id) }`. Only the first value of an Observable is
1848
+ * used. If the read fails (the HTTP interceptor toasts it) or completes
1849
+ * without a value, no confirm opens and nothing is deleted. Return
1850
+ * `undefined`, the default, to open the confirm at once. While the read
1851
+ * is in flight or the confirm is open, another delete is ignored, and a
1852
+ * screen destroyed during the read opens nothing.
1853
+ */
1854
+ getDeleteConfirmOptions(_id) {
1855
+ return undefined;
1856
+ }
1821
1857
  delete(id) {
1822
1858
  if (!id) {
1823
1859
  this.recordNotFound();
1824
1860
  return;
1825
1861
  }
1862
+ if (this.deleteConfirmBusy)
1863
+ return;
1864
+ this.deleteConfirmBusy = true;
1865
+ const details = this.getDeleteConfirmOptions(id);
1866
+ if (!details) {
1867
+ this.openDeleteConfirm(id, {});
1868
+ return;
1869
+ }
1870
+ let opened = false;
1871
+ from(details)
1872
+ // A screen left while the read was in flight opens nothing.
1873
+ .pipe(take(1), takeUntilDestroyed(this.destroyRef))
1874
+ .subscribe({
1875
+ next: (extra) => {
1876
+ opened = true;
1877
+ this.openDeleteConfirm(id, extra ?? {});
1878
+ },
1879
+ // A failed read is the interceptor's to report; without its details
1880
+ // the confirm would understate what the delete takes with it.
1881
+ error: () => {
1882
+ this.deleteConfirmBusy = false;
1883
+ },
1884
+ complete: () => {
1885
+ if (!opened)
1886
+ this.deleteConfirmBusy = false;
1887
+ },
1888
+ });
1889
+ }
1890
+ openDeleteConfirm(id, extra) {
1891
+ const { accept: _a, reject: _r, intent: _i, ...details } = extra;
1826
1892
  this.confirmDialogService.confirm({
1827
1893
  intent: 'delete',
1828
1894
  name: this.getDeleteConfirmName(id),
1829
- accept: () => this.serviceInstance.delete(id).subscribe({
1830
- next: (result) => {
1831
- if (result?.errors?.length) {
1832
- this.handleErrors(result.errors);
1833
- }
1834
- else {
1835
- this.search();
1836
- this.toastService.showSuccess();
1837
- }
1838
- },
1839
- // The HTTP error interceptor surfaces the message (toast). Swallow
1840
- // here so a rejected delete (e.g. a 422 business-rule violation)
1841
- // doesn't bubble up as an unhandled error.
1842
- error: () => undefined,
1843
- }),
1895
+ ...details,
1896
+ reject: () => {
1897
+ this.deleteConfirmBusy = false;
1898
+ },
1899
+ accept: () => {
1900
+ this.deleteConfirmBusy = false;
1901
+ this.serviceInstance.delete(id).subscribe({
1902
+ next: (result) => {
1903
+ if (result?.errors?.length) {
1904
+ this.handleErrors(result.errors);
1905
+ }
1906
+ else {
1907
+ this.search();
1908
+ this.toastService.showSuccess();
1909
+ }
1910
+ },
1911
+ // The HTTP error interceptor surfaces the message (toast). Swallow
1912
+ // here so a rejected delete (e.g. a 422 business-rule violation)
1913
+ // doesn't bubble up as an unhandled error.
1914
+ error: () => undefined,
1915
+ });
1916
+ },
1844
1917
  });
1845
1918
  }
1846
1919
  duplicate(id) {