@mintplayer/ng-spark 22.26.0 → 22.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/fesm2022/mintplayer-ng-spark-grid.mjs +71 -44
- package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs +16 -7
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +3 -2
- package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +28 -13
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +7 -2
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +1 -1
- package/fesm2022/mintplayer-ng-spark-query-list.mjs +18 -2
- package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-services.mjs +9 -7
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +4 -3
- package/fesm2022/mintplayer-ng-spark-soft-delete.mjs.map +1 -1
- package/package.json +3 -3
- package/types/mintplayer-ng-spark-grid.d.ts +47 -32
- package/types/mintplayer-ng-spark-models.d.ts +62 -27
- package/types/mintplayer-ng-spark-po-detail.d.ts +8 -2
- package/types/mintplayer-ng-spark-query-list.d.ts +11 -1
- package/types/mintplayer-ng-spark-services.d.ts +33 -8
- package/types/mintplayer-ng-spark-soft-delete.d.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-soft-delete.mjs","names":[],"sources":["../../soft-delete/src/spark-soft-delete.service.ts","../../soft-delete/src/spark-deleted-toggle.component.ts","../../soft-delete/src/spark-soft-delete-actions.component.ts","../../soft-delete/src/provide-spark-soft-delete.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { PersistentObject } from '@mintplayer/ng-spark/models';\nimport { SparkService } from '@mintplayer/ng-spark/services';\n\n/**\n * Client for the SoftDelete package's endpoints (`MintPlayer.Spark.SoftDelete`, #460 T1). Ids travel\n * in the body — the route table is literal and Raven ids contain slashes.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkSoftDeleteService {\n private readonly spark = inject(SparkService);\n\n /**\n * `POST /spark/po/restore` → the restored (live) object. Requires `Restore/T` and a deleted row;\n * anything else is 404 (or 401 anonymous), a restore withheld by `OnDisableActionsAsync` is 403.\n */\n restore(type: string, id: string): Promise<PersistentObject> {\n return this.spark.postEnvelope<PersistentObject>('/po/restore', { objectTypeId: type, id });\n }\n\n /**\n * `POST /spark/po/purge` → 204. Requires `Purge/T` and a deleted row. Removes the document and\n * every revision of it; it cannot be undone.\n */\n async purge(type: string, id: string): Promise<void> {\n await this.spark.postEnvelope<void>('/po/purge', { objectTypeId: type, id });\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkQueryListContext } from '@mintplayer/ng-spark/panels';\n\n/**\n * The query page's \"Deleted\" toggle (#460): switches the list into the recycle bin\n * (`deleted: only`) and back. Rendered only for holders of `ViewDeleted/T` — for anyone else the\n * server would ignore the widening anyway, so offering it would show the same rows under a\n * misleading label.\n *\n * Registered by {@link provideSparkSoftDelete} as a `SPARK_QUERY_LIST_ACTIONS` entry; usable on its\n * own in a custom page with any {@link SparkQueryListContext}.\n */\n@Component({\n selector: 'spark-deleted-toggle',\n imports: [TranslateKeyPipe, SparkIconComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (visible()) {\n <button type=\"button\"\n class=\"btn spark-deleted-toggle\"\n [class.btn-secondary]=\"active()\"\n [class.btn-outline-secondary]=\"!active()\"\n [attr.aria-pressed]=\"active()\"\n (click)=\"toggle()\">\n <spark-icon name=\"trash\" /> {{ 'softDelete.showDeleted' | t }}\n </button>\n }\n `,\n})\nexport class SparkDeletedToggleComponent {\n context = input.required<SparkQueryListContext>();\n\n protected readonly visible = computed(() =>\n !!this.context().entityType && this.context().permissions?.canViewDeleted === true);\n\n protected readonly active = computed(() => this.context().deleted === 'only');\n\n toggle(): void {\n this.context().setDeleted(this.active() ? 'exclude' : 'only');\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SPARK_RETURN_URL_STATE_KEY, SparkLanguageService, SparkReturnNavigationService } from '@mintplayer/ng-spark/services';\nimport { SparkDetailContext } from '@mintplayer/ng-spark/panels';\nimport { SparkSoftDeleteService } from './spark-soft-delete.service';\n\n/**\n * Restore and Purge on the detail page of a row opened from the recycle bin (#460).\n *\n * Shown only when the page loaded the row with `?deleted=only` (every row in the recycle bin is\n * deleted) and the caller holds `Restore/T` / `Purge/T`. The server re-checks both — the row must be\n * deleted and the row gate must pass — so a refusal here is the 404/403 it answers, surfaced inline.\n *\n * Registered by {@link provideSparkSoftDelete} as a `SPARK_DETAIL_ACTIONS` entry.\n */\n@Component({\n selector: 'spark-soft-delete-actions',\n imports: [TranslateKeyPipe, SparkIconComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (deletedView()) {\n <span class=\"d-inline-flex gap-2 align-items-center\">\n @if (canRestore()) {\n <button type=\"button\" class=\"btn btn-success spark-restore\" [disabled]=\"busy()\" (click)=\"restore()\">\n {{ 'softDelete.restore' | t }}\n </button>\n }\n @if (canPurge()) {\n <button type=\"button\" class=\"btn btn-danger spark-purge\" [disabled]=\"busy()\" (click)=\"purge()\">\n <spark-icon name=\"trash\" /> {{ 'softDelete.purge' | t }}\n </button>\n }\n @if (error(); as message) {\n <span class=\"text-danger small spark-soft-delete-error\" role=\"alert\">{{ message }}</span>\n }\n </span>\n }\n `,\n})\nexport class SparkSoftDeleteActionsComponent {\n private readonly softDelete = inject(SparkSoftDeleteService);\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n private readonly returnNavigation = inject(SparkReturnNavigationService);\n private readonly lang = inject(SparkLanguageService);\n\n context = input.required<SparkDetailContext>();\n\n protected readonly deletedView = computed(() => this.context().deleted === 'only');\n protected readonly canRestore = computed(() => this.context().permissions?.canRestore === true);\n protected readonly canPurge = computed(() => this.context().permissions?.canPurge === true);\n\n protected readonly busy = signal(false);\n protected readonly error = signal<string | null>(null);\n\n async restore(): Promise<void> {\n if (this.busy()) return;\n const ctx = this.context();\n await this.run(async () => {\n await this.softDelete.restore(ctx.type, ctx.id);\n // Same page, live mode: dropping `?deleted` makes the detail page re-read the row normally.\n // The list this row was opened from travels on: a later Delete still returns there.\n const returnUrl = this.returnNavigation.recordedReturnUrl();\n await this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { deleted: null },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n ...(returnUrl ? { state: { [SPARK_RETURN_URL_STATE_KEY]: returnUrl } } : {}),\n });\n });\n }\n\n async purge(): Promise<void> {\n if (this.busy()) return;\n if (!confirm(this.lang.t('softDelete.confirmPurge'))) return;\n const ctx = this.context();\n await this.run(async () => {\n await this.softDelete.purge(ctx.type, ctx.id);\n // The row no longer exists: back to the recycle bin it was opened from — the recorded list, else\n // the type's list in `?deleted=only`. Never \"the previous page\", which may be anything.\n await this.returnNavigation.returnToList(ctx.entityType?.name, { deletedView: true });\n });\n }\n\n private async run(work: () => Promise<void>): Promise<void> {\n this.busy.set(true);\n this.error.set(null);\n try {\n await work();\n } catch (e) {\n const err = e as HttpErrorResponse;\n this.error.set(err?.status === 404\n ? this.lang.t('softDelete.unavailable')\n : (err?.error?.error || err?.message || this.lang.t('common.actionFailed')));\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { Provider } from '@angular/core';\nimport { provideSparkDetailActions, provideSparkQueryListActions } from '@mintplayer/ng-spark/panels';\nimport { SparkDeletedToggleComponent } from './spark-deleted-toggle.component';\nimport { SparkSoftDeleteActionsComponent } from './spark-soft-delete-actions.component';\n\n/**\n * Adds the SoftDelete UI (#460) to the pages `sparkRoutes()` routes to:\n *\n * - the query page gets a **Deleted** toggle (holders of `ViewDeleted/T`) that lists the recycle bin\n * (`?deleted=only`); its rows open with the same mode;\n * - the detail page of such a row gets **Restore** (`Restore/T`) and **Purge** (`Purge/T`).\n *\n * Needs the server package `MintPlayer.Spark.SoftDelete`. Add to the app's providers:\n * `providers: [provideSpark(...), provideSparkSoftDelete()]`.\n */\nexport function provideSparkSoftDelete(): Provider[] {\n return [\n ...provideSparkQueryListActions({ id: 'spark-soft-delete-toggle', component: SparkDeletedToggleComponent, priority: 55 }),\n ...provideSparkDetailActions({ id: 'spark-soft-delete-actions', component: SparkSoftDeleteActionsComponent, priority: 4 }),\n ];\n}\n"],"mappings":";;;;;;;AASA,IAAa,yBAAb,MAAa,uBAAsB;CAChB,QAAQ,OAAO,YAAY;CAM5C,QAAQ,MAAc,IAAU;EAC9B,OAAO,KAAK,MAAM,aAA+B,eAAe;GAAE,cAAc;GAAM;EAAE,CAAE;CAC5F;CAMA,MAAM,MAAM,MAAc,IAAU;EAClC,MAAM,KAAK,MAAM,aAAmB,aAAa;GAAE,cAAc;GAAM;EAAE,CAAE;CAC7E;CAjBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAsB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAtB,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAsB,YADT;CAAM,CAAA;;AACnB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAsB,YAAA,CAAA;EADlC,MAAA;EAAW,MAAA,CAAA,EAAE,YAAY,OAAM,CAAE;;;ACuBlC,IAAa,8BAAb,MAAa,4BAA2B;CACtC,UAAU,MAAM,SAAiC,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAE9B,UAAU,eAC3B,CAAC,CAAC,KAAK,QAAO,CAAE,CAAC,cAAc,KAAK,QAAO,CAAE,CAAC,aAAa,mBAAmB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAElE,SAAS,eAAe,KAAK,QAAO,CAAE,CAAC,YAAY,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAE7E,SAAM;EACJ,KAAK,QAAO,CAAE,CAAC,WAAW,KAAK,OAAM,IAAK,YAAY,MAAM;CAC9D;CAVW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAA2B,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAA3B,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAA2B,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,SAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UAb5B;;;;;;;;;;;;EAWT,UAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MAb2B;GAAkB,UAAA;GAAA,QAAA,CAAA,MAAA;EAAA,GAAA;GAAA,MAAA;GAAA,MAApC;GAAgB,MAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAef,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAA2B,YAAA,CAAA;EAjBvC,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,SAAS,CAAC,kBAAkB,kBAAkB;GAC9C,iBAAiB,wBAAwB;GACzC,UAAU;;;;;;;;;;;;EAYX,CAAA;;;;;;;;;;;ACYD,IAAa,kCAAb,MAAa,gCAA+B;CACzB,aAAa,OAAO,sBAAsB;CAC1C,SAAS,OAAO,MAAM;CACtB,QAAQ,OAAO,cAAc;CAC7B,mBAAmB,OAAO,4BAA4B;CACtD,OAAO,OAAO,oBAAoB;CAEnD,UAAU,MAAM,SAA8B,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAE3B,cAAc,eAAe,KAAK,QAAO,CAAE,CAAC,YAAY,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAC/D,aAAa,eAAe,KAAK,QAAO,CAAE,CAAC,aAAa,eAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAC5E,WAAW,eAAe,KAAK,QAAO,CAAE,CAAC,aAAa,aAAa,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAExE,OAAO,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CACpB,QAAQ,OAAsB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAEtD,MAAM,UAAO;EACX,IAAI,KAAK,KAAI,GAAI;EACjB,MAAM,MAAM,KAAK,QAAO;EACxB,MAAM,KAAK,IAAI,YAAW;GACxB,MAAM,KAAK,WAAW,QAAQ,IAAI,MAAM,IAAI,EAAE;GAG9C,MAAM,YAAY,KAAK,iBAAiB,kBAAiB;GACzD,MAAM,KAAK,OAAO,SAAS,CAAA,GAAI;IAC7B,YAAY,KAAK;IACjB,aAAa,EAAE,SAAS,KAAI;IAC5B,qBAAqB;IACrB,YAAY;IACZ,GAAI,YAAY,EAAE,OAAO,GAAG,6BAA6B,UAAS,EAAE,IAAK,CAAA;GAC1E,CAAA;EACH,CAAC;CACH;CAEA,MAAM,QAAK;EACT,IAAI,KAAK,KAAI,GAAI;EACjB,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,yBAAyB,CAAC,GAAG;EACtD,MAAM,MAAM,KAAK,QAAO;EACxB,MAAM,KAAK,IAAI,YAAW;GACxB,MAAM,KAAK,WAAW,MAAM,IAAI,MAAM,IAAI,EAAE;GAG5C,MAAM,KAAK,iBAAiB,aAAa,IAAI,YAAY,MAAM,EAAE,aAAa,KAAI,CAAE;EACtF,CAAC;CACH;CAEQ,MAAM,IAAI,MAAyB;EACzC,KAAK,KAAK,IAAI,IAAI;EAClB,KAAK,MAAM,IAAI,IAAI;EACnB,IAAI;GACF,MAAM,KAAI;EACZ,SAAS,GAAG;GACV,MAAM,MAAM;GACZ,KAAK,MAAM,IAAI,KAAK,WAAW,MAC3B,KAAK,KAAK,EAAE,wBAAwB,IACnC,KAAK,OAAO,SAAS,KAAK,WAAW,KAAK,KAAK,EAAE,qBAAqB,CAAE;EAC/E,UAAU;GACR,KAAK,KAAK,IAAI,KAAK;EACrB;CACF;CA3DW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAA+B,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAA/B,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAA+B,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,SAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UApBhC;;;;;;;;;;;;;;;;;;;EAkBT,UAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MApB2B;GAAkB,UAAA;GAAA,QAAA,CAAA,MAAA;EAAA,GAAA;GAAA,MAAA;GAAA,MAApC;GAAgB,MAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAsBf,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAA+B,YAAA,CAAA;EAxB3C,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,SAAS,CAAC,kBAAkB,kBAAkB;GAC9C,iBAAiB,wBAAwB;GACzC,UAAU;;;;;;;;;;;;;;;;;;;EAmBX,CAAA;;;;;;;;;;;AC1BD,SAAgB,yBAAsB;CACpC,OAAO,CACL,GAAG,6BAA6B;EAAE,IAAI;EAA4B,WAAW;EAA6B,UAAU;CAAE,CAAE,GACxH,GAAG,0BAA0B;EAAE,IAAI;EAA6B,WAAW;EAAiC,UAAU;CAAC,CAAE,CAC1H;AACH"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-soft-delete.mjs","names":[],"sources":["../../soft-delete/src/spark-soft-delete.service.ts","../../soft-delete/src/spark-deleted-toggle.component.ts","../../soft-delete/src/spark-soft-delete-actions.component.ts","../../soft-delete/src/provide-spark-soft-delete.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { PersistentObject } from '@mintplayer/ng-spark/models';\nimport { SparkService } from '@mintplayer/ng-spark/services';\n\n/**\n * Client for the SoftDelete package's endpoints (`MintPlayer.Spark.SoftDelete`, #460 T1). Ids travel\n * in the body — the route table is literal and Raven ids contain slashes.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkSoftDeleteService {\n private readonly spark = inject(SparkService);\n\n /**\n * `POST /spark/po/restore` → the restored (live) object. Requires `Restore/T` and a deleted row;\n * anything else is 404 (or 401 anonymous), a restore withheld by `OnDisableActionsAsync` is 403.\n */\n restore(type: string, id: string): Promise<PersistentObject> {\n return this.spark.postEnvelope<PersistentObject>('/po/restore', { objectTypeId: type, id });\n }\n\n /**\n * `POST /spark/po/purge` → 204. Requires `Purge/T` and a deleted row. Removes the document and\n * every revision of it; it cannot be undone.\n */\n async purge(type: string, id: string, etag: string): Promise<void> {\n await this.spark.postEnvelope<void>('/po/purge', { objectTypeId: type, id, etag });\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SparkQueryListContext } from '@mintplayer/ng-spark/panels';\n\n/**\n * The query page's \"Deleted\" toggle (#460): switches the list into the recycle bin\n * (`deleted: only`) and back. Rendered only for holders of `ViewDeleted/T` — for anyone else the\n * server would ignore the widening anyway, so offering it would show the same rows under a\n * misleading label.\n *\n * Registered by {@link provideSparkSoftDelete} as a `SPARK_QUERY_LIST_ACTIONS` entry; usable on its\n * own in a custom page with any {@link SparkQueryListContext}.\n */\n@Component({\n selector: 'spark-deleted-toggle',\n imports: [TranslateKeyPipe, SparkIconComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (visible()) {\n <button type=\"button\"\n class=\"btn spark-deleted-toggle\"\n [class.btn-secondary]=\"active()\"\n [class.btn-outline-secondary]=\"!active()\"\n [attr.aria-pressed]=\"active()\"\n (click)=\"toggle()\">\n <spark-icon name=\"trash\" /> {{ 'softDelete.showDeleted' | t }}\n </button>\n }\n `,\n})\nexport class SparkDeletedToggleComponent {\n context = input.required<SparkQueryListContext>();\n\n protected readonly visible = computed(() =>\n !!this.context().entityType && this.context().permissions?.canViewDeleted === true);\n\n protected readonly active = computed(() => this.context().deleted === 'only');\n\n toggle(): void {\n this.context().setDeleted(this.active() ? 'exclude' : 'only');\n }\n}\n","import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';\nimport { HttpErrorResponse } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport { SPARK_RETURN_URL_STATE_KEY, SparkLanguageService, SparkReturnNavigationService } from '@mintplayer/ng-spark/services';\nimport { SparkDetailContext } from '@mintplayer/ng-spark/panels';\nimport { SparkSoftDeleteService } from './spark-soft-delete.service';\n\n/**\n * Restore and Purge on the detail page of a row opened from the recycle bin (#460).\n *\n * Shown only when the page loaded the row with `?deleted=only` (every row in the recycle bin is\n * deleted) and the caller holds `Restore/T` / `Purge/T`. The server re-checks both — the row must be\n * deleted and the row gate must pass — so a refusal here is the 404/403 it answers, surfaced inline.\n *\n * Registered by {@link provideSparkSoftDelete} as a `SPARK_DETAIL_ACTIONS` entry.\n */\n@Component({\n selector: 'spark-soft-delete-actions',\n imports: [TranslateKeyPipe, SparkIconComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (deletedView()) {\n <span class=\"d-inline-flex gap-2 align-items-center\">\n @if (canRestore()) {\n <button type=\"button\" class=\"btn btn-success spark-restore\" [disabled]=\"busy()\" (click)=\"restore()\">\n {{ 'softDelete.restore' | t }}\n </button>\n }\n @if (canPurge()) {\n <button type=\"button\" class=\"btn btn-danger spark-purge\" [disabled]=\"busy()\" (click)=\"purge()\">\n <spark-icon name=\"trash\" /> {{ 'softDelete.purge' | t }}\n </button>\n }\n @if (error(); as message) {\n <span class=\"text-danger small spark-soft-delete-error\" role=\"alert\">{{ message }}</span>\n }\n </span>\n }\n `,\n})\nexport class SparkSoftDeleteActionsComponent {\n private readonly softDelete = inject(SparkSoftDeleteService);\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n private readonly returnNavigation = inject(SparkReturnNavigationService);\n private readonly lang = inject(SparkLanguageService);\n\n context = input.required<SparkDetailContext>();\n\n protected readonly deletedView = computed(() => this.context().deleted === 'only');\n protected readonly canRestore = computed(() => this.context().permissions?.canRestore === true);\n protected readonly canPurge = computed(() => this.context().permissions?.canPurge === true);\n\n protected readonly busy = signal(false);\n protected readonly error = signal<string | null>(null);\n\n async restore(): Promise<void> {\n if (this.busy()) return;\n const ctx = this.context();\n await this.run(async () => {\n await this.softDelete.restore(ctx.type, ctx.id);\n // Same page, live mode: dropping `?deleted` makes the detail page re-read the row normally.\n // The list this row was opened from travels on: a later Delete still returns there.\n const returnUrl = this.returnNavigation.recordedReturnUrl();\n await this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { deleted: null },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n ...(returnUrl ? { state: { [SPARK_RETURN_URL_STATE_KEY]: returnUrl } } : {}),\n });\n });\n }\n\n async purge(): Promise<void> {\n if (this.busy()) return;\n if (!confirm(this.lang.t('softDelete.confirmPurge'))) return;\n const ctx = this.context();\n await this.run(async () => {\n await this.softDelete.purge(ctx.type, ctx.id, ctx.item.etag ?? '');\n // The row no longer exists: back to the recycle bin it was opened from — the recorded list, else\n // the type's list in `?deleted=only`. Never \"the previous page\", which may be anything.\n await this.returnNavigation.returnToList(ctx.entityType?.name, { deletedView: true });\n });\n }\n\n private async run(work: () => Promise<void>): Promise<void> {\n this.busy.set(true);\n this.error.set(null);\n try {\n await work();\n } catch (e) {\n const err = e as HttpErrorResponse;\n this.error.set(err?.status === 404\n ? this.lang.t('softDelete.unavailable')\n : (err?.error?.error || err?.message || this.lang.t('common.actionFailed')));\n } finally {\n this.busy.set(false);\n }\n }\n}\n","import { Provider } from '@angular/core';\nimport { provideSparkDetailActions, provideSparkQueryListActions } from '@mintplayer/ng-spark/panels';\nimport { SparkDeletedToggleComponent } from './spark-deleted-toggle.component';\nimport { SparkSoftDeleteActionsComponent } from './spark-soft-delete-actions.component';\n\n/**\n * Adds the SoftDelete UI (#460) to the pages `sparkRoutes()` routes to:\n *\n * - the query page gets a **Deleted** toggle (holders of `ViewDeleted/T`) that lists the recycle bin\n * (`?deleted=only`); its rows open with the same mode;\n * - the detail page of such a row gets **Restore** (`Restore/T`) and **Purge** (`Purge/T`).\n *\n * Needs the server package `MintPlayer.Spark.SoftDelete`. Add to the app's providers:\n * `providers: [provideSpark(...), provideSparkSoftDelete()]`.\n */\nexport function provideSparkSoftDelete(): Provider[] {\n return [\n ...provideSparkQueryListActions({ id: 'spark-soft-delete-toggle', component: SparkDeletedToggleComponent, priority: 55 }),\n ...provideSparkDetailActions({ id: 'spark-soft-delete-actions', component: SparkSoftDeleteActionsComponent, priority: 4 }),\n ];\n}\n"],"mappings":";;;;;;;AASA,IAAa,yBAAb,MAAa,uBAAsB;CAChB,QAAQ,OAAO,YAAY;CAM5C,QAAQ,MAAc,IAAU;EAC9B,OAAO,KAAK,MAAM,aAA+B,eAAe;GAAE,cAAc;GAAM;EAAE,CAAE;CAC5F;CAMA,MAAM,MAAM,MAAc,IAAY,MAAY;EAChD,MAAM,KAAK,MAAM,aAAmB,aAAa;GAAE,cAAc;GAAM;GAAI;EAAI,CAAE;CACnF;CAjBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAsB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAtB,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAsB,YADT;CAAM,CAAA;;AACnB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAsB,YAAA,CAAA;EADlC,MAAA;EAAW,MAAA,CAAA,EAAE,YAAY,OAAM,CAAE;;;ACuBlC,IAAa,8BAAb,MAAa,4BAA2B;CACtC,UAAU,MAAM,SAAiC,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAE9B,UAAU,eAC3B,CAAC,CAAC,KAAK,QAAO,CAAE,CAAC,cAAc,KAAK,QAAO,CAAE,CAAC,aAAa,mBAAmB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAElE,SAAS,eAAe,KAAK,QAAO,CAAE,CAAC,YAAY,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAE7E,SAAM;EACJ,KAAK,QAAO,CAAE,CAAC,WAAW,KAAK,OAAM,IAAK,YAAY,MAAM;CAC9D;CAVW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAA2B,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAA3B,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAA2B,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,SAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UAb5B;;;;;;;;;;;;EAWT,UAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MAb2B;GAAkB,UAAA;GAAA,QAAA,CAAA,MAAA;EAAA,GAAA;GAAA,MAAA;GAAA,MAApC;GAAgB,MAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAef,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAA2B,YAAA,CAAA;EAjBvC,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,SAAS,CAAC,kBAAkB,kBAAkB;GAC9C,iBAAiB,wBAAwB;GACzC,UAAU;;;;;;;;;;;;EAYX,CAAA;;;;;;;;;;;ACYD,IAAa,kCAAb,MAAa,gCAA+B;CACzB,aAAa,OAAO,sBAAsB;CAC1C,SAAS,OAAO,MAAM;CACtB,QAAQ,OAAO,cAAc;CAC7B,mBAAmB,OAAO,4BAA4B;CACtD,OAAO,OAAO,oBAAoB;CAEnD,UAAU,MAAM,SAA8B,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAE3B,cAAc,eAAe,KAAK,QAAO,CAAE,CAAC,YAAY,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAC/D,aAAa,eAAe,KAAK,QAAO,CAAE,CAAC,aAAa,eAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAC5E,WAAW,eAAe,KAAK,QAAO,CAAE,CAAC,aAAa,aAAa,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAExE,OAAO,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CACpB,QAAQ,OAAsB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAEtD,MAAM,UAAO;EACX,IAAI,KAAK,KAAI,GAAI;EACjB,MAAM,MAAM,KAAK,QAAO;EACxB,MAAM,KAAK,IAAI,YAAW;GACxB,MAAM,KAAK,WAAW,QAAQ,IAAI,MAAM,IAAI,EAAE;GAG9C,MAAM,YAAY,KAAK,iBAAiB,kBAAiB;GACzD,MAAM,KAAK,OAAO,SAAS,CAAA,GAAI;IAC7B,YAAY,KAAK;IACjB,aAAa,EAAE,SAAS,KAAI;IAC5B,qBAAqB;IACrB,YAAY;IACZ,GAAI,YAAY,EAAE,OAAO,GAAG,6BAA6B,UAAS,EAAE,IAAK,CAAA;GAC1E,CAAA;EACH,CAAC;CACH;CAEA,MAAM,QAAK;EACT,IAAI,KAAK,KAAI,GAAI;EACjB,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,yBAAyB,CAAC,GAAG;EACtD,MAAM,MAAM,KAAK,QAAO;EACxB,MAAM,KAAK,IAAI,YAAW;GACxB,MAAM,KAAK,WAAW,MAAM,IAAI,MAAM,IAAI,IAAI,IAAI,KAAK,QAAQ,EAAE;GAGjE,MAAM,KAAK,iBAAiB,aAAa,IAAI,YAAY,MAAM,EAAE,aAAa,KAAI,CAAE;EACtF,CAAC;CACH;CAEQ,MAAM,IAAI,MAAyB;EACzC,KAAK,KAAK,IAAI,IAAI;EAClB,KAAK,MAAM,IAAI,IAAI;EACnB,IAAI;GACF,MAAM,KAAI;EACZ,SAAS,GAAG;GACV,MAAM,MAAM;GACZ,KAAK,MAAM,IAAI,KAAK,WAAW,MAC3B,KAAK,KAAK,EAAE,wBAAwB,IACnC,KAAK,OAAO,SAAS,KAAK,WAAW,KAAK,KAAK,EAAE,qBAAqB,CAAE;EAC/E,UAAU;GACR,KAAK,KAAK,IAAI,KAAK;EACrB;CACF;CA3DW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAA+B,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAA/B,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAA+B,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,SAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UApBhC;;;;;;;;;;;;;;;;;;;EAkBT,UAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MApB2B;GAAkB,UAAA;GAAA,QAAA,CAAA,MAAA;EAAA,GAAA;GAAA,MAAA;GAAA,MAApC;GAAgB,MAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAsBf,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAA+B,YAAA,CAAA;EAxB3C,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,SAAS,CAAC,kBAAkB,kBAAkB;GAC9C,iBAAiB,wBAAwB;GACzC,UAAU;;;;;;;;;;;;;;;;;;;EAmBX,CAAA;;;;;;;;;;;AC1BD,SAAgB,yBAAsB;CACpC,OAAO,CACL,GAAG,6BAA6B;EAAE,IAAI;EAA4B,WAAW;EAA6B,UAAU;CAAE,CAAE,GACxH,GAAG,0BAA0B;EAAE,IAAI;EAA6B,WAAW;EAAiC,UAAU;CAAC,CAAE,CAC1H;AACH"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintplayer/ng-spark",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "22.
|
|
4
|
+
"version": "22.27.0",
|
|
5
5
|
"description": "Angular component library for MintPlayer.Spark CRUD applications",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"@angular/common": "^22.0.0",
|
|
15
15
|
"@angular/router": "^22.0.0",
|
|
16
16
|
"@angular/forms": "^22.0.0",
|
|
17
|
-
"@mintplayer/ng-bootstrap": "^22.
|
|
18
|
-
"@mintplayer/web-components": "^2.
|
|
17
|
+
"@mintplayer/ng-bootstrap": "^22.21.0",
|
|
18
|
+
"@mintplayer/web-components": "^2.18.0",
|
|
19
19
|
"@mintplayer/pagination": "*",
|
|
20
20
|
"rxjs": "~7.8.0"
|
|
21
21
|
},
|
|
@@ -200,21 +200,21 @@ export declare class SparkQueryActionsContext {
|
|
|
200
200
|
query: SparkQuery | null;
|
|
201
201
|
}
|
|
202
202
|
/**
|
|
203
|
-
* One button of a query's toolbar (#460,
|
|
203
|
+
* One button of a query's toolbar (#460 M15, #467 R3): the built-in New, Edit or Delete, or a custom action.
|
|
204
204
|
*
|
|
205
205
|
* Built by `SparkQueryGridComponent.toolbarActions()` and rendered by every host of the grid — the
|
|
206
206
|
* query card's header and the query-list page's action bar — so both surfaces offer the same
|
|
207
207
|
* actions, enabled by the same rules.
|
|
208
208
|
*/
|
|
209
209
|
export interface SparkQueryToolbarAction {
|
|
210
|
-
kind: 'new' | 'delete' | 'custom' | 'addon';
|
|
210
|
+
kind: 'new' | 'edit' | 'delete' | 'custom' | 'addon';
|
|
211
211
|
/** For `addon` (row menu only): the add-on's entry (`SPARK_QUERY_ROW_ACTIONS`). */
|
|
212
212
|
addon?: SparkQueryRowAction;
|
|
213
213
|
/** The action's name, unique within the toolbar. */
|
|
214
214
|
name: string;
|
|
215
215
|
/** Its catalogue entry: label, icon, `selectionRule`, `variant`, confirmation. */
|
|
216
216
|
definition: CustomActionDefinition;
|
|
217
|
-
/** `*bsPriorityNavItem` priority: New first, then Delete, then custom actions by offset. */
|
|
217
|
+
/** `*bsPriorityNavItem` priority: New first, then Edit, then Delete, then custom actions by offset. */
|
|
218
218
|
priority: number;
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
@@ -357,14 +357,13 @@ export declare class SparkQueryGridComponent {
|
|
|
357
357
|
}>;
|
|
358
358
|
/**
|
|
359
359
|
* A click anywhere on a row opens it, like its first-column link — the link used to be the only
|
|
360
|
-
* target, so a row whose first column was empty ("—") could not be opened at all.
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
360
|
+
* target, so a row whose first column was empty ("—") could not be opened at all. This holds on
|
|
361
|
+
* every list, selectable or not (#467, D9): only the checkbox cell selects, which the datatable's
|
|
362
|
+
* `'checkbox'` mode guarantees. Clicks on the link itself or on an interactive element inside the
|
|
363
|
+
* row (a vote button, a checkbox) are theirs, not the row's; a modified click (Ctrl, ⌘, Shift,
|
|
364
|
+
* middle) is left to the browser.
|
|
364
365
|
*/
|
|
365
366
|
protected onRowClick(event: BsDatatableRowEvent<unknown>): void;
|
|
366
|
-
protected onRowDblClick(event: BsDatatableRowEvent<unknown>): void;
|
|
367
|
-
private openRow;
|
|
368
367
|
/**
|
|
369
368
|
* Paging and sorting. Two-way, so a host driving `data` can sort those rows by whatever the
|
|
370
369
|
* user actually clicked — the datatable writes the new sort back through here.
|
|
@@ -449,45 +448,60 @@ export declare class SparkQueryGridComponent {
|
|
|
449
448
|
*/
|
|
450
449
|
errorMessage: import("@angular/core").WritableSignal<string | null>;
|
|
451
450
|
/**
|
|
452
|
-
* The
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
|
|
451
|
+
* The built-in actions this result offers (#467, R1(1–3)), each with its toolbar kind: listed for
|
|
452
|
+
* the caller (the right), on the query side (`defaultQueryActions` already filtered `showedOn`),
|
|
453
|
+
* not withheld by this result's `disabledActions` — Edit also not when `Save` is — and nothing in
|
|
454
|
+
* the recycle bin. New additionally needs the type's create right ({@link offersCreate}).
|
|
455
|
+
*/
|
|
456
|
+
readonly offeredDefaultActions: import("@angular/core").Signal<SparkQueryToolbarAction[]>;
|
|
457
|
+
/**
|
|
458
|
+
* The rendered selection mode (#467, R1): the host's override (a sub-query entry's
|
|
459
|
+
* `selectionMode`), else the query's own, else `'auto'` — derived from the EFFECTIVE action list
|
|
460
|
+
* (built-ins and custom actions alike), so a list is selectable exactly when the caller can use an
|
|
461
|
+
* action on ticked rows here. Recomputed whenever `disabledActions` or the deleted mode changes;
|
|
462
|
+
* the selection is cleared when the mode becomes `'none'`.
|
|
457
463
|
*/
|
|
458
464
|
selectionMode: import("@angular/core").Signal<import("@mintplayer/ng-spark/models").SparkSelectionMode>;
|
|
459
465
|
/**
|
|
460
|
-
* The actions a host renders in its toolbar, in one list (#460,
|
|
461
|
-
* actions, each with the rule that enables it. The query card and the query-list page
|
|
462
|
-
* same list, so the two surfaces cannot drift.
|
|
466
|
+
* The actions a host renders in its toolbar, in one list (#460 M15, #467 R3): New, Edit, Delete and
|
|
467
|
+
* the custom actions, each with the rule that enables it. The query card and the query-list page
|
|
468
|
+
* render this same list, so the two surfaces cannot drift. The same effective list decides
|
|
469
|
+
* {@link selectionMode}.
|
|
463
470
|
*
|
|
464
|
-
* - New:
|
|
465
|
-
*
|
|
466
|
-
* - Delete: likewise with `Delete/T` and `Delete`, and only while rows can be selected (otherwise
|
|
467
|
-
* it could never be enabled — the row menu offers it per row instead).
|
|
471
|
+
* - New, Edit, Delete: {@link offeredDefaultActions}. Edit and Delete act on ticked rows, so a list
|
|
472
|
+
* declared `'none'` leaves them to the row menu, where they would otherwise never be enabled.
|
|
468
473
|
* - Custom: `visibleCustomActions()`.
|
|
469
|
-
* - In the recycle bin ({@link recycleBin}) none
|
|
474
|
+
* - In the recycle bin ({@link recycleBin}) none: the list is empty.
|
|
470
475
|
*/
|
|
471
476
|
toolbarActions: import("@angular/core").Signal<SparkQueryToolbarAction[]>;
|
|
472
477
|
/**
|
|
473
|
-
* The per-row `⋮` menu: every offered action whose rule accepts exactly one row — Delete
|
|
474
|
-
* whatever the selection mode. An action without a rule acts on the query, not on a row,
|
|
475
|
-
* left to the toolbar.
|
|
478
|
+
* The per-row `⋮` menu: every offered action whose rule accepts exactly one row — Edit and Delete
|
|
479
|
+
* included, whatever the selection mode. An action without a rule acts on the query, not on a row,
|
|
480
|
+
* and is left to the toolbar.
|
|
476
481
|
*/
|
|
477
482
|
rowActions: import("@angular/core").Signal<SparkQueryToolbarAction[]>;
|
|
478
483
|
/** What an add-on row action is asked whether it is offered; null until the type resolved. */
|
|
479
484
|
private readonly rowActionScope;
|
|
480
|
-
/** The default Delete: not withheld by the result, and never in the recycle bin. */
|
|
481
|
-
private offersDelete;
|
|
482
485
|
/** Whether a toolbar action can run with the current selection. The server checks again. */
|
|
483
486
|
isToolbarActionEnabled(action: SparkQueryToolbarAction): boolean;
|
|
484
487
|
/** Runs a toolbar action on the current selection. */
|
|
485
488
|
runToolbarAction(action: SparkQueryToolbarAction): Promise<void>;
|
|
486
489
|
/**
|
|
487
|
-
* The
|
|
488
|
-
*
|
|
490
|
+
* The built-in Edit (#467, R2): it only navigates, to the row's edit page with this list as the
|
|
491
|
+
* return state. Get and Update enforce everything (row gate, `can`, etag, write shield); the `=1`
|
|
492
|
+
* rule is client-side only, since there is no bulk edit.
|
|
489
493
|
*/
|
|
494
|
+
editRow(row: QueryResultItem | undefined): void;
|
|
495
|
+
/** The chip's ⊗: drops the whole selection, ticked rows on other pages included. */
|
|
490
496
|
clearSelection(): void;
|
|
497
|
+
/** The ids of the rows on the page currently shown; the chip counts the ticked rows outside it. */
|
|
498
|
+
private readonly loadedIds;
|
|
499
|
+
/**
|
|
500
|
+
* Ticked rows that are not on the page shown (#467, D19): the selection survives paging, so the
|
|
501
|
+
* chip says how many of the rows an action will act on cannot be seen ("7 selected, 4 on other
|
|
502
|
+
* pages"). Actions always receive the whole `selection()`, which is what the chip counts.
|
|
503
|
+
*/
|
|
504
|
+
readonly offPageCount: import("@angular/core").Signal<number>;
|
|
491
505
|
/** The id of the row whose `⋮` menu is open; at most one is. */
|
|
492
506
|
readonly openRowMenu: import("@angular/core").WritableSignal<string | null>;
|
|
493
507
|
/** Below the toggle, right-aligned; above it when there is no room below. */
|
|
@@ -520,10 +534,11 @@ export declare class SparkQueryGridComponent {
|
|
|
520
534
|
*/
|
|
521
535
|
startNew(): void;
|
|
522
536
|
/**
|
|
523
|
-
* The default Delete on `
|
|
524
|
-
* entry's `
|
|
537
|
+
* The default Delete on `rows`: one request, all rows or none (#460, D18). Asks first — with the
|
|
538
|
+
* entry's `confirmation` (`{count}` substituted) — then clears the selection and refreshes. Each row
|
|
539
|
+
* goes with the `etag` it was listed with (#467, D14), so a row changed since is refused, not lost.
|
|
525
540
|
*/
|
|
526
|
-
deleteRows(
|
|
541
|
+
deleteRows(rows: QueryResultItem[], definition?: CustomActionDefinition): Promise<void>;
|
|
527
542
|
isVirtualScrolling: import("@angular/core").Signal<boolean>;
|
|
528
543
|
/**
|
|
529
544
|
* Columns as sent with the most recent fetched page.
|
|
@@ -128,51 +128,71 @@ export interface PersistentObjectAttribute {
|
|
|
128
128
|
*/
|
|
129
129
|
asDetailType?: string;
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* One action of the composed catalogue (#467, D7), as `/spark/actions/list` reports it for a type:
|
|
133
|
+
* a built-in (New, Edit, Delete) or a custom action, already narrowed to what the caller may run.
|
|
134
|
+
* Text arrives resolved by the server (D26).
|
|
135
|
+
*/
|
|
131
136
|
export interface CustomActionDefinition {
|
|
132
137
|
name: string;
|
|
133
|
-
|
|
138
|
+
/** `actions.{name}.label`, or the humanized name when untranslated. */
|
|
139
|
+
label: TranslatedString;
|
|
134
140
|
icon?: string;
|
|
135
|
-
description
|
|
141
|
+
/** `actions.{name}.description`; absent when untranslated. */
|
|
142
|
+
description?: TranslatedString;
|
|
136
143
|
showedOn: string;
|
|
137
144
|
selectionRule?: string;
|
|
138
145
|
refreshOnCompleted: boolean;
|
|
139
|
-
|
|
146
|
+
/**
|
|
147
|
+
* The confirmation to ask before running, `actions.{name}.confirmation` or the explicit key the
|
|
148
|
+
* catalogue names. May contain `{count}`, the number of rows about to be acted on. Absent: no prompt.
|
|
149
|
+
*/
|
|
150
|
+
confirmation?: TranslatedString;
|
|
140
151
|
/**
|
|
141
152
|
* How prominently, and how warily, to present the action: 'primary', 'secondary', 'danger',
|
|
142
153
|
* 'warning'. Undefined renders the neutral default.
|
|
143
154
|
*
|
|
144
155
|
* Presentation only — the server decides who may see and run an action. A 'danger' variant just
|
|
145
|
-
* asks the button to look like what it does; pair it with `
|
|
156
|
+
* asks the button to look like what it does; pair it with a `confirmation` for anything
|
|
146
157
|
* irreversible, because the colour warns and the prompt is what actually prevents the accident.
|
|
147
158
|
*/
|
|
148
159
|
variant?: string;
|
|
149
160
|
offset: number;
|
|
150
161
|
/**
|
|
151
|
-
* True for the framework's built-in `New` and `Delete` (#460,
|
|
152
|
-
* `showedOn` and a `selectionRule` like any custom action (
|
|
153
|
-
*
|
|
154
|
-
* `/po/delete-many`, never `/actions/execute`. Absent for a custom action.
|
|
162
|
+
* True for the framework's built-in `New`, `Edit` and `Delete` (#460 D18, #467 D7): catalogue
|
|
163
|
+
* entries with a `showedOn` and a `selectionRule` like any custom action (the core's
|
|
164
|
+
* `actions.json` ships them; an app overrides or removes them in its own), but run through
|
|
165
|
+
* `/po/new`, the edit page and `/po/delete-many`, never `/actions/execute`. Absent for a custom action.
|
|
155
166
|
*/
|
|
156
167
|
isDefault?: boolean;
|
|
157
168
|
}
|
|
158
|
-
/** How many rows a grid lets the user select — what `<bs-datatable>` renders. */
|
|
159
|
-
export type SparkSelectionMode = 'none' | 'single' | 'multiple';
|
|
160
169
|
/**
|
|
161
|
-
*
|
|
170
|
+
* Whether a grid lets the user tick rows — what `<bs-datatable>` renders. There is no single
|
|
171
|
+
* selection (#467, D10): a selectable list is a checkbox list, and actions whose rule does not
|
|
172
|
+
* match the count are disabled rather than the selection trimmed.
|
|
173
|
+
*/
|
|
174
|
+
export type SparkSelectionMode = 'none' | 'multiple';
|
|
175
|
+
/**
|
|
176
|
+
* What a query definition (or a sub-query entry) declares (#460 D17): a fixed mode, or `'auto'` —
|
|
162
177
|
* the default, and what an absent field means — to derive it from the actions offered.
|
|
163
178
|
*/
|
|
164
179
|
export type SparkSelectionModeSetting = 'auto' | SparkSelectionMode;
|
|
180
|
+
/** The largest selection the server accepts in one request (`SparkDefaultActions.MaxSelectedItems`). */
|
|
181
|
+
export declare const SPARK_MAX_SELECTED_ITEMS = 200;
|
|
165
182
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
*
|
|
183
|
+
* Whether an action needs ticked rows (#467, R1(4)): it has a rule, and the rule accepts at least
|
|
184
|
+
* one row. `=0` accepts none, so an action that runs only without a selection never makes a list
|
|
185
|
+
* selectable; an action without a rule acts on the query, not on rows.
|
|
186
|
+
*/
|
|
187
|
+
export declare function needsSelection(action: CustomActionDefinition): boolean;
|
|
188
|
+
/**
|
|
189
|
+
* The selection mode a grid renders (#467, R1).
|
|
173
190
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
191
|
+
* A declared `'none'` or `'multiple'` wins outright. `'auto'` (or nothing) derives it from
|
|
192
|
+
* `actions`, which must already be the **effective** list for this result — listed for the caller
|
|
193
|
+
* (the right), shown on queries, not withheld by `disabledActions` and not hidden by the recycle
|
|
194
|
+
* bin. Built-in Edit and Delete count like any other action. `'multiple'` when at least one of them
|
|
195
|
+
* {@link needsSelection}, else `'none'`, so a read-only user sees a list with no checkbox column.
|
|
176
196
|
*/
|
|
177
197
|
export declare function selectionModeFor(actions: CustomActionDefinition[], declared?: SparkSelectionModeSetting | null): SparkSelectionMode;
|
|
178
198
|
/**
|
|
@@ -293,7 +313,8 @@ export interface AttributeGroup {
|
|
|
293
313
|
export interface EntityType {
|
|
294
314
|
id: string;
|
|
295
315
|
name: string;
|
|
296
|
-
description
|
|
316
|
+
/** The type's display name, resolved by the server (#467, D6; formerly `description`). */
|
|
317
|
+
label?: TranslatedString;
|
|
297
318
|
/**
|
|
298
319
|
* The backing CLR type, or absent for a JSON-only virtual type (#325).
|
|
299
320
|
*
|
|
@@ -369,7 +390,8 @@ export interface SparkQuerySortColumn {
|
|
|
369
390
|
export interface SparkQuery {
|
|
370
391
|
id: string;
|
|
371
392
|
name: string;
|
|
372
|
-
description
|
|
393
|
+
/** The query's display name, resolved by the server (#467; formerly `description`). */
|
|
394
|
+
label?: TranslatedString;
|
|
373
395
|
source: string;
|
|
374
396
|
alias?: string;
|
|
375
397
|
sortColumns: SparkQuerySortColumn[];
|
|
@@ -479,14 +501,20 @@ export interface QueryColumn extends SparkCellColumn {
|
|
|
479
501
|
/**
|
|
480
502
|
* One row: an id, a display string, and a value per column.
|
|
481
503
|
*
|
|
482
|
-
* Deliberately too weak to act on — no attribute metadata, no `can` block,
|
|
483
|
-
*
|
|
504
|
+
* Deliberately too weak to act on — no attribute metadata, no `can` block, because neither can be
|
|
505
|
+
* trusted from a projection. It does carry its document's `etag` (#467, D14), a version rather than
|
|
506
|
+
* a judgement, so a delete from the list says which version it removes.
|
|
484
507
|
*/
|
|
485
508
|
export interface QueryResultItem {
|
|
486
509
|
/** Never null and unique within a result; the server refuses anything else. */
|
|
487
510
|
id: string;
|
|
488
511
|
/** What to show when the row is named rather than tabulated (a reference picker's value). */
|
|
489
512
|
breadcrumb?: string;
|
|
513
|
+
/**
|
|
514
|
+
* The document's change vector when the list loaded (#467, D14). A delete sends it back; a row
|
|
515
|
+
* changed since is a 409. Absent for a row with no document behind it.
|
|
516
|
+
*/
|
|
517
|
+
etag?: string;
|
|
490
518
|
values: QueryResultItemValue[];
|
|
491
519
|
/** Presentation hints for the whole row. Keys arrive lower-cased. */
|
|
492
520
|
typeHints?: Record<string, string>;
|
|
@@ -926,6 +954,13 @@ export declare function comparableValue(attribute: PersistentObjectAttribute | u
|
|
|
926
954
|
export declare function comparableFormValue(value: unknown): string;
|
|
927
955
|
/** Whether two form-dict values hold the same content (see {@link comparableFormValue}). */
|
|
928
956
|
export declare function formValuesEqual(a: unknown, b: unknown): boolean;
|
|
957
|
+
/**
|
|
958
|
+
* The prompt to show before running `action` on `count` rows, with `{count}` substituted, or null
|
|
959
|
+
* when the action asks for no confirmation (#467, D7).
|
|
960
|
+
*/
|
|
961
|
+
export declare function confirmationText(action: CustomActionDefinition | undefined | null, count: number, lang?: string): string | null;
|
|
962
|
+
/** The built-in action named `name` (`New`, `Edit`, `Delete`) in `actions`, when the server listed it. */
|
|
963
|
+
export declare function builtInAction(actions: CustomActionDefinition[], name: 'New' | 'Edit' | 'Delete'): CustomActionDefinition | undefined;
|
|
929
964
|
/** Whether `showedOn` offers the action on a query: `"query"` or `"both"`, in any casing. */
|
|
930
965
|
export declare function isShowedOnQuery(showedOn: string | undefined | null): boolean;
|
|
931
966
|
/** Whether `showedOn` offers the action on a detail page: `"detail"` or `"both"`, in any casing. */
|
|
@@ -937,7 +972,7 @@ export declare function isShowedOnDetail(showedOn: string | undefined | null): b
|
|
|
937
972
|
* and `"both"` — as the server model and the custom-actions guide have always
|
|
938
973
|
* documented — compared case-insensitively (#460 M15: `"Both"` used to render nowhere).
|
|
939
974
|
*
|
|
940
|
-
* The built-in New and Delete (`isDefault`, #460 D18) are left out: they are not executed as
|
|
975
|
+
* The built-in New, Edit and Delete (`isDefault`, #460 D18, #467 D7) are left out: they are not executed as
|
|
941
976
|
* custom actions, and the grid offers them itself (see {@link defaultQueryActions}).
|
|
942
977
|
*
|
|
943
978
|
* ⚠️ This narrows what is DISPLAYED. It is NOT an authorization boundary: the grant
|
|
@@ -948,8 +983,8 @@ export declare function filterQueryActions(actions: CustomActionDefinition[]): C
|
|
|
948
983
|
/** The custom actions a detail page should offer — the detail-side counterpart of {@link filterQueryActions}. */
|
|
949
984
|
export declare function filterDetailActions(actions: CustomActionDefinition[]): CustomActionDefinition[];
|
|
950
985
|
/**
|
|
951
|
-
* The built-in New and Delete the server listed for this caller (#460,
|
|
952
|
-
* includes the query side. Present only when the caller holds `New/T` or `Delete/T`.
|
|
986
|
+
* The built-in New, Edit and Delete the server listed for this caller (#460 D18, #467 D7), when their `showedOn`
|
|
987
|
+
* includes the query side. Present only when the caller holds `New/T`, `Edit/T` or `Delete/T`.
|
|
953
988
|
*/
|
|
954
989
|
export declare function defaultQueryActions(actions: CustomActionDefinition[]): CustomActionDefinition[];
|
|
955
990
|
/**
|
|
@@ -60,8 +60,12 @@ export declare class SparkPoDetailComponent {
|
|
|
60
60
|
asDetailReferenceOptions: import("@angular/core").WritableSignal<Record<string, Record<string, QueryResultItem[]>>>;
|
|
61
61
|
type: string;
|
|
62
62
|
id: string;
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
/** The catalogue's Edit (#467, D8) when this page offers it: listed for the caller, on the detail side, and allowed on this row. */
|
|
64
|
+
editAction: import("@angular/core").WritableSignal<CustomActionDefinition | null>;
|
|
65
|
+
/** The catalogue's Delete, on the same terms as {@link editAction}. */
|
|
66
|
+
deleteAction: import("@angular/core").WritableSignal<CustomActionDefinition | null>;
|
|
67
|
+
canEdit: import("@angular/core").Signal<boolean>;
|
|
68
|
+
canDelete: import("@angular/core").Signal<boolean>;
|
|
65
69
|
customActions: import("@angular/core").WritableSignal<CustomActionDefinition[]>;
|
|
66
70
|
/** Type-level rights, kept for the add-on context; null until loaded. */
|
|
67
71
|
permissions: import("@angular/core").WritableSignal<EntityPermissions | null>;
|
|
@@ -125,6 +129,8 @@ export declare class SparkPoDetailComponent {
|
|
|
125
129
|
* a louder style. Restricted to a known set rather than interpolated, because the value arrives
|
|
126
130
|
* from a JSON file and must not be able to put arbitrary classes on the button.
|
|
127
131
|
*/
|
|
132
|
+
/** Edit's and Delete's button classes: the catalogue's variant, else the look they always had. */
|
|
133
|
+
protected builtInActionClass(action: CustomActionDefinition, fallback: 'primary' | 'danger'): string;
|
|
128
134
|
protected customActionClass(action: CustomActionDefinition): string;
|
|
129
135
|
/**
|
|
130
136
|
* The action currently running, or null. Drives the disabled state on every custom-action
|
|
@@ -3,7 +3,7 @@ import { TemplateRef } from "@angular/core";
|
|
|
3
3
|
import { Color } from "@mintplayer/ng-bootstrap";
|
|
4
4
|
import { SparkLanguageService } from "@mintplayer/ng-spark/services";
|
|
5
5
|
import { SparkQueryToolbarAction } from "@mintplayer/ng-spark/grid";
|
|
6
|
-
import { CustomActionDefinition, QueryColumn, QueryColumnFilter, QueryResultItem, SparkDeletedFilter } from "@mintplayer/ng-spark/models";
|
|
6
|
+
import { CustomActionDefinition, QueryColumn, QueryColumnFilter, QueryResultItem, SparkDeletedFilter, SparkSelectionModeSetting } from "@mintplayer/ng-spark/models";
|
|
7
7
|
import { SparkQueryListContext } from "@mintplayer/ng-spark/panels";
|
|
8
8
|
/**
|
|
9
9
|
* The routed query page: chrome around one {@link SparkQueryGridComponent}.
|
|
@@ -31,6 +31,11 @@ export declare class SparkQueryListComponent {
|
|
|
31
31
|
protected readonly lang: SparkLanguageService;
|
|
32
32
|
extraActionsTemplate: import("@angular/core").InputSignal<TemplateRef<void> | null>;
|
|
33
33
|
showCustomActions: import("@angular/core").InputSignal<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Overrides the query's own `selectionMode` (#467, R3), as the sub-query card's input does: for a
|
|
36
|
+
* host that embeds this page and wants, say, `'none'` on a list the actions would make selectable.
|
|
37
|
+
*/
|
|
38
|
+
selectionMode: import("@angular/core").InputSignal<SparkSelectionModeSetting | null>;
|
|
34
39
|
/**
|
|
35
40
|
* Forwarded to the grid, so a query PAGE can replace its row links — the same reason the card
|
|
36
41
|
* forwards it. Without this the escape hatch existed only for a directly-embedded grid.
|
|
@@ -147,6 +152,11 @@ export declare class SparkQueryListComponent {
|
|
|
147
152
|
"required": false;
|
|
148
153
|
"isSignal": true;
|
|
149
154
|
};
|
|
155
|
+
"selectionMode": {
|
|
156
|
+
"alias": "selectionMode";
|
|
157
|
+
"required": false;
|
|
158
|
+
"isSignal": true;
|
|
159
|
+
};
|
|
150
160
|
"rowRoute": {
|
|
151
161
|
"alias": "rowRoute";
|
|
152
162
|
"required": false;
|
|
@@ -62,11 +62,22 @@ export interface NewObjectOptions {
|
|
|
62
62
|
}
|
|
63
63
|
/** Where a bulk delete was started from; see {@link SparkService.deleteMany}. */
|
|
64
64
|
export interface DeleteManyOptions {
|
|
65
|
-
/**
|
|
66
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The query the rows were selected in. Required (#467, D12): the server fetches the rows through it
|
|
67
|
+
* and applies its `OnDisableActionsAsync` decision; without it the call is a 400.
|
|
68
|
+
*/
|
|
69
|
+
queryId: string;
|
|
67
70
|
/** The sub-query's container, when deleting from a sub-query. */
|
|
68
71
|
parentId?: string;
|
|
69
72
|
parentType?: string;
|
|
73
|
+
/** One reason for the whole batch, recorded on every soft-deleted row (#467, D20). */
|
|
74
|
+
reason?: string;
|
|
75
|
+
}
|
|
76
|
+
/** A row and the version of it the caller saw (#467, D14): what a bulk delete names. */
|
|
77
|
+
export interface SparkRowVersion {
|
|
78
|
+
id: string;
|
|
79
|
+
/** The row's `etag` from the query result. */
|
|
80
|
+
etag: string;
|
|
70
81
|
}
|
|
71
82
|
/** Context for {@link SparkService.deleteRow}. Every field is required — see `DeleteRow.cs`. */
|
|
72
83
|
export interface DeleteRowOptions {
|
|
@@ -155,12 +166,20 @@ export declare class SparkService {
|
|
|
155
166
|
* `parent` is the sub-query the New was started from — the same `parentId`/`parentType`/`queryId`
|
|
156
167
|
* `newObject` was given. The server resolves and authorizes it again and hands it to the save hooks
|
|
157
168
|
* as `PersistentObject.Parent`.
|
|
169
|
+
*
|
|
170
|
+
* Resolves to `null` when a server interceptor cancelled the create (#482: the user answered a prompt with
|
|
171
|
+
* Cancel) — the server answers 204 and nothing was created.
|
|
158
172
|
*/
|
|
159
173
|
create(type: string, data: Partial<PersistentObject>, parent?: {
|
|
160
174
|
parentId: string;
|
|
161
175
|
parentType: string;
|
|
162
176
|
queryId: string;
|
|
163
|
-
}): Promise<PersistentObject>;
|
|
177
|
+
}): Promise<PersistentObject | null>;
|
|
178
|
+
/**
|
|
179
|
+
* Saves an edit. `data.etag` — the etag the object was loaded with — is required (#467, D16): it is
|
|
180
|
+
* the version being edited. A 409 means the object changed since (`reason: 'changed'`) or was
|
|
181
|
+
* deleted since (`reason: 'deleted'`).
|
|
182
|
+
*/
|
|
164
183
|
update(type: string, id: string, data: Partial<PersistentObject>): Promise<PersistentObject>;
|
|
165
184
|
/**
|
|
166
185
|
* Asks the server to reshape an in-progress object after `triggeredBy`'s value changed.
|
|
@@ -199,14 +218,20 @@ export declare class SparkService {
|
|
|
199
218
|
* of the route variables.
|
|
200
219
|
*/
|
|
201
220
|
deleteRow(type: string, options: DeleteRowOptions): Promise<void>;
|
|
202
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Deletes one object. `etag` is the version being deleted — the loaded object's `etag` — and is
|
|
223
|
+
* required (#467, D14): an object changed since is a 409.
|
|
224
|
+
*/
|
|
225
|
+
delete(type: string, id: string, etag: string): Promise<void>;
|
|
203
226
|
/**
|
|
204
227
|
* Deletes several rows of one type in one request — the default Delete action on a selection
|
|
205
|
-
* (`POST /spark/po/delete-many`, #460 D18).
|
|
206
|
-
*
|
|
207
|
-
*
|
|
228
|
+
* (`POST /spark/po/delete-many`, #460 D18). Each row carries the `etag` its query result row had
|
|
229
|
+
* (#467, D14). All or nothing: a 404 means some row is missing or not yours to delete, a 403 that
|
|
230
|
+
* the server's `OnDisableActionsAsync` withholds Delete on the query or on one of the rows, a 409
|
|
231
|
+
* that rows changed since the list loaded (the body's `message` names them), a 400 that the
|
|
232
|
+
* selection breaks the rule or the 200-row cap.
|
|
208
233
|
*/
|
|
209
|
-
deleteMany(type: string,
|
|
234
|
+
deleteMany(type: string, items: SparkRowVersion[], options: DeleteManyOptions): Promise<void>;
|
|
210
235
|
getCustomActions(objectTypeId: string): Promise<CustomActionDefinition[]>;
|
|
211
236
|
/**
|
|
212
237
|
* @param parent The object of THIS type the action is operating on — the detail-page invocation.
|
|
@@ -17,7 +17,7 @@ export declare class SparkSoftDeleteService {
|
|
|
17
17
|
* `POST /spark/po/purge` → 204. Requires `Purge/T` and a deleted row. Removes the document and
|
|
18
18
|
* every revision of it; it cannot be undone.
|
|
19
19
|
*/
|
|
20
|
-
purge(type: string, id: string): Promise<void>;
|
|
20
|
+
purge(type: string, id: string, etag: string): Promise<void>;
|
|
21
21
|
static ɵfac: i0.ɵɵFactoryDeclaration<SparkSoftDeleteService, never>;
|
|
22
22
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
23
23
|
}
|