@dignite/vault-extract 0.2.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 +17 -0
- package/fesm2022/dignite-vault-extract-config.mjs +82 -0
- package/fesm2022/dignite-vault-extract-config.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-cabinet-list.component-Ch0gpSCc.mjs +184 -0
- package/fesm2022/dignite-vault-extract-documents-cabinet-list.component-Ch0gpSCc.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-content-type-DjCs-s4E.mjs +115 -0
- package/fesm2022/dignite-vault-extract-documents-content-type-DjCs-s4E.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-document-detail.component-DHs42DWJ.mjs +1146 -0
- package/fesm2022/dignite-vault-extract-documents-document-detail.component-DHs42DWJ.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-document-file-preview.component-CStXf8v9.mjs +72 -0
- package/fesm2022/dignite-vault-extract-documents-document-file-preview.component-CStXf8v9.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-document-list.component-jThR5cct.mjs +642 -0
- package/fesm2022/dignite-vault-extract-documents-document-list.component-jThR5cct.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-document-overview.component-BHUUUIVr.mjs +318 -0
- package/fesm2022/dignite-vault-extract-documents-document-overview.component-BHUUUIVr.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-document-recycle-bin.component-dqeBrw22.mjs +178 -0
- package/fesm2022/dignite-vault-extract-documents-document-recycle-bin.component-dqeBrw22.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-document-type-list.component-C8kXFJGb.mjs +464 -0
- package/fesm2022/dignite-vault-extract-documents-document-type-list.component-C8kXFJGb.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-export-template-list.component-DlmZFFF1.mjs +361 -0
- package/fesm2022/dignite-vault-extract-documents-export-template-list.component-DlmZFFF1.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-extensible-table-DkLXuoWo.mjs +53 -0
- package/fesm2022/dignite-vault-extract-documents-extensible-table-DkLXuoWo.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-field-definition-list.component-ClmWkRun.mjs +530 -0
- package/fesm2022/dignite-vault-extract-documents-field-definition-list.component-ClmWkRun.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-field-reextraction-modal.component-D7OOycv9.mjs +163 -0
- package/fesm2022/dignite-vault-extract-documents-field-reextraction-modal.component-D7OOycv9.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-format-bytes-Cd3QwfQZ.mjs +19 -0
- package/fesm2022/dignite-vault-extract-documents-format-bytes-Cd3QwfQZ.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents-format-field-value-Xjb8lwzA.mjs +22 -0
- package/fesm2022/dignite-vault-extract-documents-format-field-value-Xjb8lwzA.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract-documents.mjs +71 -0
- package/fesm2022/dignite-vault-extract-documents.mjs.map +1 -0
- package/fesm2022/dignite-vault-extract.mjs +522 -0
- package/fesm2022/dignite-vault-extract.mjs.map +1 -0
- package/package.json +38 -0
- package/types/dignite-vault-extract-config.d.ts +5 -0
- package/types/dignite-vault-extract-documents.d.ts +5 -0
- package/types/dignite-vault-extract.d.ts +521 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/dignite-projects/vault-extract/main/.github/icon.png" width="128" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @dignite/vault-extract
|
|
6
|
+
|
|
7
|
+
Angular UI library for [Dignite Vault Extract](https://github.com/dignite-projects/vault-extract) — the channel layer that turns scans, photos, PDF images, and Office files into trustworthy structured data (Markdown + metadata) for downstream RAG platforms, business systems, and AI clients.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @dignite/vault-extract
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Documentation
|
|
16
|
+
|
|
17
|
+
See the [project repository](https://github.com/dignite-projects/vault-extract) for full documentation.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { RoutesService } from '@abp/ng.core';
|
|
2
|
+
import { makeEnvironmentProviders, provideAppInitializer, inject } from '@angular/core';
|
|
3
|
+
import { EXTRACT_PERMISSIONS } from '@dignite/vault-extract';
|
|
4
|
+
|
|
5
|
+
function provideExtract() {
|
|
6
|
+
return makeEnvironmentProviders([
|
|
7
|
+
provideAppInitializer(() => {
|
|
8
|
+
const routes = inject(RoutesService);
|
|
9
|
+
routes.add([
|
|
10
|
+
{
|
|
11
|
+
path: '/documents',
|
|
12
|
+
name: '::Menu:Documents',
|
|
13
|
+
iconClass: 'fas fa-file-alt',
|
|
14
|
+
requiredPolicy: EXTRACT_PERMISSIONS.Documents.Default,
|
|
15
|
+
order: 2,
|
|
16
|
+
layout: "application" /* eLayoutType.application */,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
path: '/documents/overview',
|
|
20
|
+
name: '::Menu:DocumentOverview',
|
|
21
|
+
iconClass: 'fas fa-chart-bar',
|
|
22
|
+
parentName: '::Menu:Documents',
|
|
23
|
+
requiredPolicy: EXTRACT_PERMISSIONS.Documents.Default,
|
|
24
|
+
order: 0,
|
|
25
|
+
layout: "application" /* eLayoutType.application */,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
path: '/documents/list',
|
|
29
|
+
name: '::Menu:DocumentList',
|
|
30
|
+
iconClass: 'fas fa-list',
|
|
31
|
+
parentName: '::Menu:Documents',
|
|
32
|
+
requiredPolicy: EXTRACT_PERMISSIONS.Documents.Default,
|
|
33
|
+
order: 1,
|
|
34
|
+
layout: "application" /* eLayoutType.application */,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: '/documents/types',
|
|
38
|
+
name: '::Menu:DocumentTypes',
|
|
39
|
+
iconClass: 'fas fa-tags',
|
|
40
|
+
parentName: '::Menu:Documents',
|
|
41
|
+
requiredPolicy: EXTRACT_PERMISSIONS.DocumentTypes.Default,
|
|
42
|
+
order: 3,
|
|
43
|
+
layout: "application" /* eLayoutType.application */,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
path: '/documents/export-templates',
|
|
47
|
+
name: '::Menu:ExportTemplates',
|
|
48
|
+
iconClass: 'fas fa-file-export',
|
|
49
|
+
parentName: '::Menu:Documents',
|
|
50
|
+
requiredPolicy: EXTRACT_PERMISSIONS.Documents.Templates.Default,
|
|
51
|
+
order: 4,
|
|
52
|
+
layout: "application" /* eLayoutType.application */,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
path: '/documents/cabinets',
|
|
56
|
+
name: '::Menu:Cabinets',
|
|
57
|
+
iconClass: 'fas fa-folder',
|
|
58
|
+
parentName: '::Menu:Documents',
|
|
59
|
+
requiredPolicy: EXTRACT_PERMISSIONS.Cabinets.Default,
|
|
60
|
+
order: 5,
|
|
61
|
+
layout: "application" /* eLayoutType.application */,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: '/documents/recycle',
|
|
65
|
+
name: '::Menu:DocumentRecycleBin',
|
|
66
|
+
iconClass: 'fas fa-trash-can',
|
|
67
|
+
parentName: '::Menu:Documents',
|
|
68
|
+
requiredPolicy: EXTRACT_PERMISSIONS.Documents.Restore,
|
|
69
|
+
order: 6,
|
|
70
|
+
layout: "application" /* eLayoutType.application */,
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
}),
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generated bundle index. Do not edit.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
export { provideExtract };
|
|
82
|
+
//# sourceMappingURL=dignite-vault-extract-config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dignite-vault-extract-config.mjs","sources":["../../../packages/vault-extract/config/src/providers/documents.provider.ts","../../../packages/vault-extract/config/src/dignite-vault-extract-config.ts"],"sourcesContent":["import { eLayoutType, RoutesService } from '@abp/ng.core';\nimport {\n EnvironmentProviders,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport { EXTRACT_PERMISSIONS } from '@dignite/vault-extract';\n\nexport function provideExtract(): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideAppInitializer(() => {\n const routes = inject(RoutesService);\n routes.add([\n {\n path: '/documents',\n name: '::Menu:Documents',\n iconClass: 'fas fa-file-alt',\n requiredPolicy: EXTRACT_PERMISSIONS.Documents.Default,\n order: 2,\n layout: eLayoutType.application,\n },\n {\n path: '/documents/overview',\n name: '::Menu:DocumentOverview',\n iconClass: 'fas fa-chart-bar',\n parentName: '::Menu:Documents',\n requiredPolicy: EXTRACT_PERMISSIONS.Documents.Default,\n order: 0,\n layout: eLayoutType.application,\n },\n {\n path: '/documents/list',\n name: '::Menu:DocumentList',\n iconClass: 'fas fa-list',\n parentName: '::Menu:Documents',\n requiredPolicy: EXTRACT_PERMISSIONS.Documents.Default,\n order: 1,\n layout: eLayoutType.application,\n },\n {\n path: '/documents/types',\n name: '::Menu:DocumentTypes',\n iconClass: 'fas fa-tags',\n parentName: '::Menu:Documents',\n requiredPolicy: EXTRACT_PERMISSIONS.DocumentTypes.Default,\n order: 3,\n layout: eLayoutType.application,\n },\n {\n path: '/documents/export-templates',\n name: '::Menu:ExportTemplates',\n iconClass: 'fas fa-file-export',\n parentName: '::Menu:Documents',\n requiredPolicy: EXTRACT_PERMISSIONS.Documents.Templates.Default,\n order: 4,\n layout: eLayoutType.application,\n },\n {\n path: '/documents/cabinets',\n name: '::Menu:Cabinets',\n iconClass: 'fas fa-folder',\n parentName: '::Menu:Documents',\n requiredPolicy: EXTRACT_PERMISSIONS.Cabinets.Default,\n order: 5,\n layout: eLayoutType.application,\n },\n {\n path: '/documents/recycle',\n name: '::Menu:DocumentRecycleBin',\n iconClass: 'fas fa-trash-can',\n parentName: '::Menu:Documents',\n requiredPolicy: EXTRACT_PERMISSIONS.Documents.Restore,\n order: 6,\n layout: eLayoutType.application,\n },\n ]);\n }),\n ]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;SASgB,cAAc,GAAA;AAC5B,IAAA,OAAO,wBAAwB,CAAC;QAC9B,qBAAqB,CAAC,MAAK;AACzB,YAAA,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC;AACT,gBAAA;AACE,oBAAA,IAAI,EAAE,YAAY;AAClB,oBAAA,IAAI,EAAE,kBAAkB;AACxB,oBAAA,SAAS,EAAE,iBAAiB;AAC5B,oBAAA,cAAc,EAAE,mBAAmB,CAAC,SAAS,CAAC,OAAO;AACrD,oBAAA,KAAK,EAAE,CAAC;AACR,oBAAA,MAAM,EAAA,aAAA;AACP,iBAAA;AACD,gBAAA;AACE,oBAAA,IAAI,EAAE,qBAAqB;AAC3B,oBAAA,IAAI,EAAE,yBAAyB;AAC/B,oBAAA,SAAS,EAAE,kBAAkB;AAC7B,oBAAA,UAAU,EAAE,kBAAkB;AAC9B,oBAAA,cAAc,EAAE,mBAAmB,CAAC,SAAS,CAAC,OAAO;AACrD,oBAAA,KAAK,EAAE,CAAC;AACR,oBAAA,MAAM,EAAA,aAAA;AACP,iBAAA;AACD,gBAAA;AACE,oBAAA,IAAI,EAAE,iBAAiB;AACvB,oBAAA,IAAI,EAAE,qBAAqB;AAC3B,oBAAA,SAAS,EAAE,aAAa;AACxB,oBAAA,UAAU,EAAE,kBAAkB;AAC9B,oBAAA,cAAc,EAAE,mBAAmB,CAAC,SAAS,CAAC,OAAO;AACrD,oBAAA,KAAK,EAAE,CAAC;AACR,oBAAA,MAAM,EAAA,aAAA;AACP,iBAAA;AACD,gBAAA;AACE,oBAAA,IAAI,EAAE,kBAAkB;AACxB,oBAAA,IAAI,EAAE,sBAAsB;AAC5B,oBAAA,SAAS,EAAE,aAAa;AACxB,oBAAA,UAAU,EAAE,kBAAkB;AAC9B,oBAAA,cAAc,EAAE,mBAAmB,CAAC,aAAa,CAAC,OAAO;AACzD,oBAAA,KAAK,EAAE,CAAC;AACR,oBAAA,MAAM,EAAA,aAAA;AACP,iBAAA;AACD,gBAAA;AACE,oBAAA,IAAI,EAAE,6BAA6B;AACnC,oBAAA,IAAI,EAAE,wBAAwB;AAC9B,oBAAA,SAAS,EAAE,oBAAoB;AAC/B,oBAAA,UAAU,EAAE,kBAAkB;AAC9B,oBAAA,cAAc,EAAE,mBAAmB,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO;AAC/D,oBAAA,KAAK,EAAE,CAAC;AACR,oBAAA,MAAM,EAAA,aAAA;AACP,iBAAA;AACD,gBAAA;AACE,oBAAA,IAAI,EAAE,qBAAqB;AAC3B,oBAAA,IAAI,EAAE,iBAAiB;AACvB,oBAAA,SAAS,EAAE,eAAe;AAC1B,oBAAA,UAAU,EAAE,kBAAkB;AAC9B,oBAAA,cAAc,EAAE,mBAAmB,CAAC,QAAQ,CAAC,OAAO;AACpD,oBAAA,KAAK,EAAE,CAAC;AACR,oBAAA,MAAM,EAAA,aAAA;AACP,iBAAA;AACD,gBAAA;AACE,oBAAA,IAAI,EAAE,oBAAoB;AAC1B,oBAAA,IAAI,EAAE,2BAA2B;AACjC,oBAAA,SAAS,EAAE,kBAAkB;AAC7B,oBAAA,UAAU,EAAE,kBAAkB;AAC9B,oBAAA,cAAc,EAAE,mBAAmB,CAAC,SAAS,CAAC,OAAO;AACrD,oBAAA,KAAK,EAAE,CAAC;AACR,oBAAA,MAAM,EAAA,aAAA;AACP,iBAAA;AACF,aAAA,CAAC;AACJ,QAAA,CAAC,CAAC;AACH,KAAA,CAAC;AACJ;;AC/EA;;AAEG;;;;"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, DestroyRef, signal, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import * as i1 from '@angular/forms';
|
|
6
|
+
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
7
|
+
import { PermissionService, ListService, escapeHtmlChars, LocalizationPipe } from '@abp/ng.core';
|
|
8
|
+
import { ExtensionsService, EntityProp, ExtensibleTableComponent, EXTENSIONS_IDENTIFIER } from '@abp/ng.components/extensible';
|
|
9
|
+
import { ConfirmationService, ToasterService, Confirmation } from '@abp/ng.theme.shared';
|
|
10
|
+
import * as i2 from '@ng-bootstrap/ng-bootstrap';
|
|
11
|
+
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
|
|
12
|
+
import { of } from 'rxjs';
|
|
13
|
+
import { CabinetService, EXTRACT_PERMISSIONS } from '@dignite/vault-extract';
|
|
14
|
+
import { c as configureEntityTable, E as EXTRACT_TABLES, p as pageClientItems } from './dignite-vault-extract-documents-extensible-table-DkLXuoWo.mjs';
|
|
15
|
+
|
|
16
|
+
// Mirrors CabinetConsts (Domain.Shared): Name / Description length caps.
|
|
17
|
+
const MAX_NAME_LENGTH = 128;
|
|
18
|
+
const MAX_DESCRIPTION_LENGTH = 512;
|
|
19
|
+
const CABINET_SORTS = {
|
|
20
|
+
name: cabinet => cabinet.name,
|
|
21
|
+
};
|
|
22
|
+
class CabinetListComponent {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.service = inject(CabinetService);
|
|
25
|
+
this.fb = inject(FormBuilder);
|
|
26
|
+
this.confirmation = inject(ConfirmationService);
|
|
27
|
+
this.toaster = inject(ToasterService);
|
|
28
|
+
this.permissionService = inject(PermissionService);
|
|
29
|
+
this.destroyRef = inject(DestroyRef);
|
|
30
|
+
this.extensions = inject(ExtensionsService);
|
|
31
|
+
this.list = inject(ListService);
|
|
32
|
+
this.canCreate = this.permissionService.getGrantedPolicy(EXTRACT_PERMISSIONS.Cabinets.Create);
|
|
33
|
+
this.canUpdate = this.permissionService.getGrantedPolicy(EXTRACT_PERMISSIONS.Cabinets.Update);
|
|
34
|
+
this.canDelete = this.permissionService.getGrantedPolicy(EXTRACT_PERMISSIONS.Cabinets.Delete);
|
|
35
|
+
this.allCabinets = signal([], ...(ngDevMode ? [{ debugName: "allCabinets" }] : /* istanbul ignore next */ []));
|
|
36
|
+
this.cabinets = signal({ totalCount: 0, items: [] }, ...(ngDevMode ? [{ debugName: "cabinets" }] : /* istanbul ignore next */ []));
|
|
37
|
+
this.isLoading = signal(true, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
38
|
+
// null = closed; 'create' / CabinetDto = open in the matching mode.
|
|
39
|
+
this.editing = signal(null, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
|
|
40
|
+
this.isSubmitting = signal(false, ...(ngDevMode ? [{ debugName: "isSubmitting" }] : /* istanbul ignore next */ []));
|
|
41
|
+
this.tableQuery = {};
|
|
42
|
+
this.form = this.fb.nonNullable.group({
|
|
43
|
+
name: ['', [Validators.required, Validators.maxLength(MAX_NAME_LENGTH)]],
|
|
44
|
+
description: ['', [Validators.maxLength(MAX_DESCRIPTION_LENGTH)]],
|
|
45
|
+
});
|
|
46
|
+
configureEntityTable(this.extensions, EXTRACT_TABLES.Cabinets, [
|
|
47
|
+
EntityProp.create({
|
|
48
|
+
type: "string" /* ePropType.String */,
|
|
49
|
+
name: 'name',
|
|
50
|
+
displayName: '::Cabinet:Name',
|
|
51
|
+
sortable: true,
|
|
52
|
+
valueResolver: data => {
|
|
53
|
+
const name = escapeHtmlChars(data.record.name);
|
|
54
|
+
const description = data.record.description
|
|
55
|
+
? `<div class="small text-muted fw-normal mt-1">${escapeHtmlChars(data.record.description)}</div>`
|
|
56
|
+
: '';
|
|
57
|
+
return of(`<span class="fw-semibold"><i class="fas fa-folder text-warning me-2"></i>${name}</span>${description}`);
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
ngOnInit() {
|
|
63
|
+
this.hookTableQuery();
|
|
64
|
+
this.load();
|
|
65
|
+
}
|
|
66
|
+
refresh() {
|
|
67
|
+
this.load();
|
|
68
|
+
}
|
|
69
|
+
load() {
|
|
70
|
+
this.isLoading.set(true);
|
|
71
|
+
this.service.getList()
|
|
72
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
73
|
+
.subscribe({
|
|
74
|
+
next: list => {
|
|
75
|
+
this.allCabinets.set(list);
|
|
76
|
+
this.list.totalCount = list.length;
|
|
77
|
+
this.applyTableQuery();
|
|
78
|
+
this.isLoading.set(false);
|
|
79
|
+
},
|
|
80
|
+
error: () => {
|
|
81
|
+
this.allCabinets.set([]);
|
|
82
|
+
this.cabinets.set({ totalCount: 0, items: [] });
|
|
83
|
+
this.list.totalCount = 0;
|
|
84
|
+
this.isLoading.set(false);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
hookTableQuery() {
|
|
89
|
+
this.list.query$
|
|
90
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
91
|
+
.subscribe(query => this.applyTableQuery(query));
|
|
92
|
+
}
|
|
93
|
+
applyTableQuery(query = this.tableQuery) {
|
|
94
|
+
this.tableQuery = query;
|
|
95
|
+
this.cabinets.set(pageClientItems(this.allCabinets(), query, CABINET_SORTS));
|
|
96
|
+
}
|
|
97
|
+
openCreate() {
|
|
98
|
+
this.form.reset({ name: '', description: '' });
|
|
99
|
+
this.editing.set('create');
|
|
100
|
+
}
|
|
101
|
+
openEdit(cabinet) {
|
|
102
|
+
this.form.reset({ name: cabinet.name, description: cabinet.description ?? '' });
|
|
103
|
+
this.editing.set(cabinet);
|
|
104
|
+
}
|
|
105
|
+
closeModal() {
|
|
106
|
+
this.editing.set(null);
|
|
107
|
+
}
|
|
108
|
+
submit() {
|
|
109
|
+
if (this.form.invalid) {
|
|
110
|
+
this.form.markAllAsTouched();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const mode = this.editing();
|
|
114
|
+
if (mode === null)
|
|
115
|
+
return;
|
|
116
|
+
this.isSubmitting.set(true);
|
|
117
|
+
const raw = this.form.getRawValue();
|
|
118
|
+
// Normalize blank descriptions to undefined; backend ValidateDescription also treats blanks as
|
|
119
|
+
// "no description".
|
|
120
|
+
const description = raw.description.trim() || undefined;
|
|
121
|
+
if (mode === 'create') {
|
|
122
|
+
const input = { name: raw.name, description };
|
|
123
|
+
this.service.create(input)
|
|
124
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
125
|
+
.subscribe({
|
|
126
|
+
next: () => this.onSaved('::Cabinet:CreatedSuccessfully'),
|
|
127
|
+
error: () => this.isSubmitting.set(false),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
this.service.update(mode.id, { name: raw.name, description })
|
|
132
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
133
|
+
.subscribe({
|
|
134
|
+
next: () => this.onSaved('::Cabinet:UpdatedSuccessfully'),
|
|
135
|
+
error: () => this.isSubmitting.set(false),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
onSaved(messageKey) {
|
|
140
|
+
this.isSubmitting.set(false);
|
|
141
|
+
this.closeModal();
|
|
142
|
+
this.toaster.success(messageKey, '::Success');
|
|
143
|
+
this.load();
|
|
144
|
+
}
|
|
145
|
+
delete(cabinet) {
|
|
146
|
+
this.confirmation
|
|
147
|
+
.warn('::Cabinet:AreYouSureToDelete', '::AreYouSure')
|
|
148
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
149
|
+
.subscribe(status => {
|
|
150
|
+
if (status !== Confirmation.Status.confirm)
|
|
151
|
+
return;
|
|
152
|
+
this.service.delete(cabinet.id)
|
|
153
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
154
|
+
.subscribe({
|
|
155
|
+
next: () => {
|
|
156
|
+
this.toaster.success('::Cabinet:DeletedSuccessfully', '::Success');
|
|
157
|
+
this.load();
|
|
158
|
+
},
|
|
159
|
+
error: () => this.toaster.error('::Cabinet:DeleteFailed', '::Error'),
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CabinetListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
164
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: CabinetListComponent, isStandalone: true, selector: "lib-cabinet-list", providers: [
|
|
165
|
+
ListService,
|
|
166
|
+
{
|
|
167
|
+
provide: EXTENSIONS_IDENTIFIER,
|
|
168
|
+
useValue: EXTRACT_TABLES.Cabinets,
|
|
169
|
+
},
|
|
170
|
+
], ngImport: i0, template: "<div class=\"container-fluid py-4\">\n <!-- Header -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h4 class=\"mb-0\">\n <i class=\"fas fa-folder me-2\"></i>\n {{ '::Cabinet:Title' | abpLocalization }}\n </h4>\n <div class=\"d-flex gap-2\">\n <button\n class=\"btn btn-outline-secondary\"\n (click)=\"refresh()\"\n [disabled]=\"isLoading()\"\n title=\"{{ '::Refresh' | abpLocalization }}\"\n >\n <i class=\"fas fa-sync-alt\" [class.fa-spin]=\"isLoading()\"></i>\n </button>\n @if (canCreate) {\n <button class=\"btn btn-primary\" (click)=\"openCreate()\">\n <i class=\"fas fa-plus me-1\"></i>\n {{ '::Cabinet:New' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n\n <!-- Loading spinner -->\n @if (isLoading()) {\n <div class=\"text-center py-5\">\n <div class=\"spinner-border text-primary\" role=\"status\"></div>\n </div>\n }\n\n <!-- Empty state -->\n @if (!isLoading() && cabinets().totalCount === 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body text-center py-5\">\n <i class=\"fas fa-folder-open fa-3x text-muted mb-3 d-block\"></i>\n <p class=\"text-muted mb-0\">{{ '::Cabinet:Empty' | abpLocalization }}</p>\n </div>\n </div>\n }\n\n <!-- Cabinets table -->\n @if (!isLoading() && cabinets().totalCount > 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body p-0\">\n <ng-template #actionsTemplate let-cabinet>\n @if (canUpdate || canDelete) {\n <div ngbDropdown container=\"body\" class=\"d-inline-block\">\n <button type=\"button\" class=\"btn btn-sm btn-primary\" ngbDropdownToggle>\n {{ 'AbpUi::Actions' | abpLocalization }}\n </button>\n <div ngbDropdownMenu>\n @if (canUpdate) {\n <button type=\"button\" ngbDropdownItem (click)=\"openEdit(cabinet)\">\n <i class=\"fas fa-pen me-2\"></i>\n {{ '::Edit' | abpLocalization }}\n </button>\n }\n @if (canDelete) {\n <button type=\"button\" ngbDropdownItem (click)=\"delete(cabinet)\">\n <i class=\"fas fa-trash me-2\"></i>\n {{ '::Delete' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n }\n </ng-template>\n\n <abp-extensible-table\n [data]=\"cabinets().items\"\n [recordsTotal]=\"cabinets().totalCount\"\n [list]=\"list\"\n [actionsTemplate]=\"actionsTemplate\"\n actionsText=\"AbpUi::Actions\"\n [actionsColumnWidth]=\"150\"\n />\n </div>\n </div>\n }\n</div>\n\n<!-- Create / Edit modal -->\n@if (editing(); as mode) {\n <div class=\"modal d-block\" tabindex=\"-1\" style=\"background:rgba(0,0,0,.4);\" (click)=\"closeModal()\">\n <div class=\"modal-dialog modal-dialog-centered\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-content\">\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"fas fa-folder me-2\"></i>\n {{ (mode === 'create' ? '::Cabinet:New' : '::Cabinet:Edit') | abpLocalization }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeModal()\"></button>\n </div>\n <div class=\"modal-body\">\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::Cabinet:Name' | abpLocalization }}</label>\n <input\n type=\"text\"\n class=\"form-control\"\n formControlName=\"name\"\n [class.is-invalid]=\"form.controls.name.touched && form.controls.name.invalid\"\n />\n <div class=\"form-text\">{{ '::Cabinet:NameHint' | abpLocalization }}</div>\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::Cabinet:Description' | abpLocalization }}</label>\n <textarea\n class=\"form-control\"\n formControlName=\"description\"\n rows=\"3\"\n [class.is-invalid]=\"form.controls.description.touched && form.controls.description.invalid\"\n ></textarea>\n <div class=\"form-text\">{{ '::Cabinet:DescriptionHint' | abpLocalization }}</div>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"closeModal()\">\n {{ '::Cancel' | abpLocalization }}\n </button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"form.invalid || isSubmitting()\">\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n }\n {{ '::Save' | abpLocalization }}\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ExtensibleTableComponent, selector: "abp-extensible-table", inputs: ["actionsText", "data", "list", "recordsTotal", "actionsColumnWidth", "actionsTemplate", "selectable", "selectionType", "selected", "infiniteScroll", "isLoading", "scrollThreshold", "tableHeight", "rowDetailTemplate", "rowDetailHeight"], outputs: ["tableActivate", "selectionChange", "loadMore", "rowDetailToggle"], exportAs: ["abpExtensibleTable"] }, { kind: "ngmodule", type: NgbDropdownModule }, { kind: "directive", type: i2.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i2.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i2.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "directive", type: i2.NgbDropdownItem, selector: "[ngbDropdownItem]", inputs: ["tabindex", "disabled"] }, { kind: "directive", type: i2.NgbDropdownButtonItem, selector: "button[ngbDropdownItem]" }, { kind: "pipe", type: LocalizationPipe, name: "abpLocalization" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
171
|
+
}
|
|
172
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CabinetListComponent, decorators: [{
|
|
173
|
+
type: Component,
|
|
174
|
+
args: [{ selector: 'lib-cabinet-list', imports: [CommonModule, ReactiveFormsModule, LocalizationPipe, ExtensibleTableComponent, NgbDropdownModule], providers: [
|
|
175
|
+
ListService,
|
|
176
|
+
{
|
|
177
|
+
provide: EXTENSIONS_IDENTIFIER,
|
|
178
|
+
useValue: EXTRACT_TABLES.Cabinets,
|
|
179
|
+
},
|
|
180
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"container-fluid py-4\">\n <!-- Header -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h4 class=\"mb-0\">\n <i class=\"fas fa-folder me-2\"></i>\n {{ '::Cabinet:Title' | abpLocalization }}\n </h4>\n <div class=\"d-flex gap-2\">\n <button\n class=\"btn btn-outline-secondary\"\n (click)=\"refresh()\"\n [disabled]=\"isLoading()\"\n title=\"{{ '::Refresh' | abpLocalization }}\"\n >\n <i class=\"fas fa-sync-alt\" [class.fa-spin]=\"isLoading()\"></i>\n </button>\n @if (canCreate) {\n <button class=\"btn btn-primary\" (click)=\"openCreate()\">\n <i class=\"fas fa-plus me-1\"></i>\n {{ '::Cabinet:New' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n\n <!-- Loading spinner -->\n @if (isLoading()) {\n <div class=\"text-center py-5\">\n <div class=\"spinner-border text-primary\" role=\"status\"></div>\n </div>\n }\n\n <!-- Empty state -->\n @if (!isLoading() && cabinets().totalCount === 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body text-center py-5\">\n <i class=\"fas fa-folder-open fa-3x text-muted mb-3 d-block\"></i>\n <p class=\"text-muted mb-0\">{{ '::Cabinet:Empty' | abpLocalization }}</p>\n </div>\n </div>\n }\n\n <!-- Cabinets table -->\n @if (!isLoading() && cabinets().totalCount > 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body p-0\">\n <ng-template #actionsTemplate let-cabinet>\n @if (canUpdate || canDelete) {\n <div ngbDropdown container=\"body\" class=\"d-inline-block\">\n <button type=\"button\" class=\"btn btn-sm btn-primary\" ngbDropdownToggle>\n {{ 'AbpUi::Actions' | abpLocalization }}\n </button>\n <div ngbDropdownMenu>\n @if (canUpdate) {\n <button type=\"button\" ngbDropdownItem (click)=\"openEdit(cabinet)\">\n <i class=\"fas fa-pen me-2\"></i>\n {{ '::Edit' | abpLocalization }}\n </button>\n }\n @if (canDelete) {\n <button type=\"button\" ngbDropdownItem (click)=\"delete(cabinet)\">\n <i class=\"fas fa-trash me-2\"></i>\n {{ '::Delete' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n }\n </ng-template>\n\n <abp-extensible-table\n [data]=\"cabinets().items\"\n [recordsTotal]=\"cabinets().totalCount\"\n [list]=\"list\"\n [actionsTemplate]=\"actionsTemplate\"\n actionsText=\"AbpUi::Actions\"\n [actionsColumnWidth]=\"150\"\n />\n </div>\n </div>\n }\n</div>\n\n<!-- Create / Edit modal -->\n@if (editing(); as mode) {\n <div class=\"modal d-block\" tabindex=\"-1\" style=\"background:rgba(0,0,0,.4);\" (click)=\"closeModal()\">\n <div class=\"modal-dialog modal-dialog-centered\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-content\">\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"fas fa-folder me-2\"></i>\n {{ (mode === 'create' ? '::Cabinet:New' : '::Cabinet:Edit') | abpLocalization }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeModal()\"></button>\n </div>\n <div class=\"modal-body\">\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::Cabinet:Name' | abpLocalization }}</label>\n <input\n type=\"text\"\n class=\"form-control\"\n formControlName=\"name\"\n [class.is-invalid]=\"form.controls.name.touched && form.controls.name.invalid\"\n />\n <div class=\"form-text\">{{ '::Cabinet:NameHint' | abpLocalization }}</div>\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::Cabinet:Description' | abpLocalization }}</label>\n <textarea\n class=\"form-control\"\n formControlName=\"description\"\n rows=\"3\"\n [class.is-invalid]=\"form.controls.description.touched && form.controls.description.invalid\"\n ></textarea>\n <div class=\"form-text\">{{ '::Cabinet:DescriptionHint' | abpLocalization }}</div>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"closeModal()\">\n {{ '::Cancel' | abpLocalization }}\n </button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"form.invalid || isSubmitting()\">\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n }\n {{ '::Save' | abpLocalization }}\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n}\n" }]
|
|
181
|
+
}], ctorParameters: () => [] });
|
|
182
|
+
|
|
183
|
+
export { CabinetListComponent };
|
|
184
|
+
//# sourceMappingURL=dignite-vault-extract-documents-cabinet-list.component-Ch0gpSCc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dignite-vault-extract-documents-cabinet-list.component-Ch0gpSCc.mjs","sources":["../../../packages/vault-extract/documents/src/lib/cabinets/cabinet-list/cabinet-list.component.ts","../../../packages/vault-extract/documents/src/lib/cabinets/cabinet-list/cabinet-list.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n OnInit,\n inject,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { CommonModule } from '@angular/common';\nimport { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { escapeHtmlChars, ListService, LocalizationPipe, PermissionService } from '@abp/ng.core';\nimport type { ABP } from '@abp/ng.core';\nimport {\n EntityProp,\n EXTENSIONS_IDENTIFIER,\n ExtensionsService,\n ExtensibleTableComponent,\n ePropType,\n} from '@abp/ng.components/extensible';\nimport { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared';\nimport { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';\nimport { of } from 'rxjs';\nimport {\n CabinetDto,\n CabinetService,\n CreateCabinetDto,\n EXTRACT_PERMISSIONS,\n} from '@dignite/vault-extract';\nimport {\n ClientPagedResult,\n configureEntityTable,\n pageClientItems,\n EXTRACT_TABLES,\n SortAccessors,\n} from '../../shared/extensible-table';\n\n// Mirrors CabinetConsts (Domain.Shared): Name / Description length caps.\nconst MAX_NAME_LENGTH = 128;\nconst MAX_DESCRIPTION_LENGTH = 512;\n\nconst CABINET_SORTS: SortAccessors<CabinetDto> = {\n name: cabinet => cabinet.name,\n};\n\n@Component({\n selector: 'lib-cabinet-list',\n templateUrl: './cabinet-list.component.html',\n styleUrls: ['./cabinet-list.component.scss'],\n imports: [CommonModule, ReactiveFormsModule, LocalizationPipe, ExtensibleTableComponent, NgbDropdownModule],\n providers: [\n ListService,\n {\n provide: EXTENSIONS_IDENTIFIER,\n useValue: EXTRACT_TABLES.Cabinets,\n },\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CabinetListComponent implements OnInit {\n private readonly service = inject(CabinetService);\n private readonly fb = inject(FormBuilder);\n private readonly confirmation = inject(ConfirmationService);\n private readonly toaster = inject(ToasterService);\n private readonly permissionService = inject(PermissionService);\n private readonly destroyRef = inject(DestroyRef);\n private readonly extensions = inject(ExtensionsService);\n\n readonly list = inject(ListService);\n\n readonly canCreate = this.permissionService.getGrantedPolicy(EXTRACT_PERMISSIONS.Cabinets.Create);\n readonly canUpdate = this.permissionService.getGrantedPolicy(EXTRACT_PERMISSIONS.Cabinets.Update);\n readonly canDelete = this.permissionService.getGrantedPolicy(EXTRACT_PERMISSIONS.Cabinets.Delete);\n\n allCabinets = signal<CabinetDto[]>([]);\n cabinets = signal<ClientPagedResult<CabinetDto>>({ totalCount: 0, items: [] });\n isLoading = signal(true);\n\n // null = closed; 'create' / CabinetDto = open in the matching mode.\n editing = signal<CabinetDto | 'create' | null>(null);\n isSubmitting = signal(false);\n private tableQuery: Partial<ABP.PageQueryParams> = {};\n\n readonly form = this.fb.nonNullable.group({\n name: ['', [Validators.required, Validators.maxLength(MAX_NAME_LENGTH)]],\n description: ['', [Validators.maxLength(MAX_DESCRIPTION_LENGTH)]],\n });\n\n constructor() {\n configureEntityTable<CabinetDto>(this.extensions, EXTRACT_TABLES.Cabinets, [\n EntityProp.create<CabinetDto>({\n type: ePropType.String,\n name: 'name',\n displayName: '::Cabinet:Name',\n sortable: true,\n valueResolver: data => {\n const name = escapeHtmlChars(data.record.name);\n const description = data.record.description\n ? `<div class=\"small text-muted fw-normal mt-1\">${escapeHtmlChars(data.record.description)}</div>`\n : '';\n return of(`<span class=\"fw-semibold\"><i class=\"fas fa-folder text-warning me-2\"></i>${name}</span>${description}`);\n },\n }),\n ]);\n }\n\n ngOnInit(): void {\n this.hookTableQuery();\n this.load();\n }\n\n refresh(): void {\n this.load();\n }\n\n private load(): void {\n this.isLoading.set(true);\n this.service.getList()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: list => {\n this.allCabinets.set(list);\n this.list.totalCount = list.length;\n this.applyTableQuery();\n this.isLoading.set(false);\n },\n error: () => {\n this.allCabinets.set([]);\n this.cabinets.set({ totalCount: 0, items: [] });\n this.list.totalCount = 0;\n this.isLoading.set(false);\n },\n });\n }\n\n private hookTableQuery(): void {\n this.list.query$\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(query => this.applyTableQuery(query));\n }\n\n private applyTableQuery(query: Partial<ABP.PageQueryParams> = this.tableQuery): void {\n this.tableQuery = query;\n this.cabinets.set(pageClientItems(this.allCabinets(), query, CABINET_SORTS));\n }\n\n openCreate(): void {\n this.form.reset({ name: '', description: '' });\n this.editing.set('create');\n }\n\n openEdit(cabinet: CabinetDto): void {\n this.form.reset({ name: cabinet.name, description: cabinet.description ?? '' });\n this.editing.set(cabinet);\n }\n\n closeModal(): void {\n this.editing.set(null);\n }\n\n submit(): void {\n if (this.form.invalid) {\n this.form.markAllAsTouched();\n return;\n }\n const mode = this.editing();\n if (mode === null) return;\n\n this.isSubmitting.set(true);\n const raw = this.form.getRawValue();\n // Normalize blank descriptions to undefined; backend ValidateDescription also treats blanks as\n // \"no description\".\n const description = raw.description.trim() || undefined;\n\n if (mode === 'create') {\n const input: CreateCabinetDto = { name: raw.name, description };\n this.service.create(input)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: () => this.onSaved('::Cabinet:CreatedSuccessfully'),\n error: () => this.isSubmitting.set(false),\n });\n } else {\n this.service.update(mode.id!, { name: raw.name, description })\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: () => this.onSaved('::Cabinet:UpdatedSuccessfully'),\n error: () => this.isSubmitting.set(false),\n });\n }\n }\n\n private onSaved(messageKey: string): void {\n this.isSubmitting.set(false);\n this.closeModal();\n this.toaster.success(messageKey, '::Success');\n this.load();\n }\n\n delete(cabinet: CabinetDto): void {\n this.confirmation\n .warn('::Cabinet:AreYouSureToDelete', '::AreYouSure')\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(status => {\n if (status !== Confirmation.Status.confirm) return;\n this.service.delete(cabinet.id!)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: () => {\n this.toaster.success('::Cabinet:DeletedSuccessfully', '::Success');\n this.load();\n },\n error: () => this.toaster.error('::Cabinet:DeleteFailed', '::Error'),\n });\n });\n }\n}\n","<div class=\"container-fluid py-4\">\n <!-- Header -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h4 class=\"mb-0\">\n <i class=\"fas fa-folder me-2\"></i>\n {{ '::Cabinet:Title' | abpLocalization }}\n </h4>\n <div class=\"d-flex gap-2\">\n <button\n class=\"btn btn-outline-secondary\"\n (click)=\"refresh()\"\n [disabled]=\"isLoading()\"\n title=\"{{ '::Refresh' | abpLocalization }}\"\n >\n <i class=\"fas fa-sync-alt\" [class.fa-spin]=\"isLoading()\"></i>\n </button>\n @if (canCreate) {\n <button class=\"btn btn-primary\" (click)=\"openCreate()\">\n <i class=\"fas fa-plus me-1\"></i>\n {{ '::Cabinet:New' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n\n <!-- Loading spinner -->\n @if (isLoading()) {\n <div class=\"text-center py-5\">\n <div class=\"spinner-border text-primary\" role=\"status\"></div>\n </div>\n }\n\n <!-- Empty state -->\n @if (!isLoading() && cabinets().totalCount === 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body text-center py-5\">\n <i class=\"fas fa-folder-open fa-3x text-muted mb-3 d-block\"></i>\n <p class=\"text-muted mb-0\">{{ '::Cabinet:Empty' | abpLocalization }}</p>\n </div>\n </div>\n }\n\n <!-- Cabinets table -->\n @if (!isLoading() && cabinets().totalCount > 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body p-0\">\n <ng-template #actionsTemplate let-cabinet>\n @if (canUpdate || canDelete) {\n <div ngbDropdown container=\"body\" class=\"d-inline-block\">\n <button type=\"button\" class=\"btn btn-sm btn-primary\" ngbDropdownToggle>\n {{ 'AbpUi::Actions' | abpLocalization }}\n </button>\n <div ngbDropdownMenu>\n @if (canUpdate) {\n <button type=\"button\" ngbDropdownItem (click)=\"openEdit(cabinet)\">\n <i class=\"fas fa-pen me-2\"></i>\n {{ '::Edit' | abpLocalization }}\n </button>\n }\n @if (canDelete) {\n <button type=\"button\" ngbDropdownItem (click)=\"delete(cabinet)\">\n <i class=\"fas fa-trash me-2\"></i>\n {{ '::Delete' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n }\n </ng-template>\n\n <abp-extensible-table\n [data]=\"cabinets().items\"\n [recordsTotal]=\"cabinets().totalCount\"\n [list]=\"list\"\n [actionsTemplate]=\"actionsTemplate\"\n actionsText=\"AbpUi::Actions\"\n [actionsColumnWidth]=\"150\"\n />\n </div>\n </div>\n }\n</div>\n\n<!-- Create / Edit modal -->\n@if (editing(); as mode) {\n <div class=\"modal d-block\" tabindex=\"-1\" style=\"background:rgba(0,0,0,.4);\" (click)=\"closeModal()\">\n <div class=\"modal-dialog modal-dialog-centered\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-content\">\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"fas fa-folder me-2\"></i>\n {{ (mode === 'create' ? '::Cabinet:New' : '::Cabinet:Edit') | abpLocalization }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeModal()\"></button>\n </div>\n <div class=\"modal-body\">\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::Cabinet:Name' | abpLocalization }}</label>\n <input\n type=\"text\"\n class=\"form-control\"\n formControlName=\"name\"\n [class.is-invalid]=\"form.controls.name.touched && form.controls.name.invalid\"\n />\n <div class=\"form-text\">{{ '::Cabinet:NameHint' | abpLocalization }}</div>\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::Cabinet:Description' | abpLocalization }}</label>\n <textarea\n class=\"form-control\"\n formControlName=\"description\"\n rows=\"3\"\n [class.is-invalid]=\"form.controls.description.touched && form.controls.description.invalid\"\n ></textarea>\n <div class=\"form-text\">{{ '::Cabinet:DescriptionHint' | abpLocalization }}</div>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"closeModal()\">\n {{ '::Cancel' | abpLocalization }}\n </button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"form.invalid || isSubmitting()\">\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n }\n {{ '::Save' | abpLocalization }}\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, DestroyRef, signal, Injectable } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
5
|
+
import { DocumentService } from '@dignite/vault-extract';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Owns the original-file blob lifecycle for the document preview path (#277): fetch via
|
|
9
|
+
* DocumentService.getBlob (Bearer token, never in the URL) → createObjectURL → sanitize for the
|
|
10
|
+
* PDF iframe → revoke on destroy. Both the detail page's Original File tab and the standalone
|
|
11
|
+
* file-preview page consume this single seam so the lifecycle can no longer drift across two
|
|
12
|
+
* hand-copied implementations.
|
|
13
|
+
*
|
|
14
|
+
* NOT `providedIn: 'root'` — declared in each consuming component's `providers`, so one instance
|
|
15
|
+
* lives per component and its `ngOnDestroy` revoke is scoped to that component's lifetime.
|
|
16
|
+
*/
|
|
17
|
+
class DocumentFileBlobService {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.documentService = inject(DocumentService);
|
|
20
|
+
this.sanitizer = inject(DomSanitizer);
|
|
21
|
+
this.destroyRef = inject(DestroyRef);
|
|
22
|
+
// raw blob: URL — used for <img> src, download anchors/trigger, and ngOnDestroy revoke.
|
|
23
|
+
this.blobUrl = signal(null, ...(ngDevMode ? [{ debugName: "blobUrl" }] : /* istanbul ignore next */ []));
|
|
24
|
+
// PDF iframe resource URL must pass through DomSanitizer or Angular drops it as unsafe
|
|
25
|
+
// (self-minted same-origin blob:, safe to trust).
|
|
26
|
+
this.safeResourceUrl = signal(null, ...(ngDevMode ? [{ debugName: "safeResourceUrl" }] : /* istanbul ignore next */ []));
|
|
27
|
+
this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
28
|
+
// Unified failure signal: blob download failed, or an <img>/iframe render failed (markError).
|
|
29
|
+
this.hasError = signal(false, ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Lazy-load the blob once. Idempotent: short-circuits when already cached or a request is in
|
|
33
|
+
* flight, so it is safe to call from a Tab activation, a Refresh, or a download click without
|
|
34
|
+
* duplicate fetches. Observe outcome via the `blobUrl` / `isLoading` / `hasError` signals.
|
|
35
|
+
*/
|
|
36
|
+
ensureLoaded(documentId) {
|
|
37
|
+
if (this.blobUrl() || this.isLoading())
|
|
38
|
+
return;
|
|
39
|
+
this.isLoading.set(true);
|
|
40
|
+
this.hasError.set(false);
|
|
41
|
+
this.documentService
|
|
42
|
+
.getBlob(documentId)
|
|
43
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
44
|
+
.subscribe({
|
|
45
|
+
next: blob => {
|
|
46
|
+
const url = URL.createObjectURL(blob);
|
|
47
|
+
this.blobUrl.set(url);
|
|
48
|
+
this.safeResourceUrl.set(this.sanitizer.bypassSecurityTrustResourceUrl(url));
|
|
49
|
+
this.isLoading.set(false);
|
|
50
|
+
},
|
|
51
|
+
error: () => {
|
|
52
|
+
this.isLoading.set(false);
|
|
53
|
+
this.hasError.set(true);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Clear the error so an <img>/iframe rebuild retries; keeps any cached blob.
|
|
58
|
+
resetError() {
|
|
59
|
+
this.hasError.set(false);
|
|
60
|
+
}
|
|
61
|
+
// Drop the cached blob entirely and return to the pristine state. Used when the host component is reused
|
|
62
|
+
// for a different document (the detail page reacts to :id route changes instead of being re-created), so
|
|
63
|
+
// the previous document's blob never leaks into the new one's preview. Revokes the object URL — a fresh
|
|
64
|
+
// ensureLoaded() will fetch and mint a new one.
|
|
65
|
+
reset() {
|
|
66
|
+
const url = this.blobUrl();
|
|
67
|
+
if (url)
|
|
68
|
+
URL.revokeObjectURL(url);
|
|
69
|
+
this.blobUrl.set(null);
|
|
70
|
+
this.safeResourceUrl.set(null);
|
|
71
|
+
this.isLoading.set(false);
|
|
72
|
+
this.hasError.set(false);
|
|
73
|
+
}
|
|
74
|
+
// Flag a render failure (e.g. <img> decode error after the blob downloaded fine).
|
|
75
|
+
markError() {
|
|
76
|
+
this.hasError.set(true);
|
|
77
|
+
}
|
|
78
|
+
// Trigger a browser download from the cached blob via a hidden <a download>. Does not revoke —
|
|
79
|
+
// the same URL may still back an inline preview; ngOnDestroy owns the single revoke.
|
|
80
|
+
download(fileName) {
|
|
81
|
+
const url = this.blobUrl();
|
|
82
|
+
if (!url)
|
|
83
|
+
return;
|
|
84
|
+
const anchor = document.createElement('a');
|
|
85
|
+
anchor.href = url;
|
|
86
|
+
anchor.download = fileName;
|
|
87
|
+
anchor.click();
|
|
88
|
+
}
|
|
89
|
+
ngOnDestroy() {
|
|
90
|
+
const url = this.blobUrl();
|
|
91
|
+
if (url)
|
|
92
|
+
URL.revokeObjectURL(url);
|
|
93
|
+
}
|
|
94
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DocumentFileBlobService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
95
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DocumentFileBlobService }); }
|
|
96
|
+
}
|
|
97
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DocumentFileBlobService, decorators: [{
|
|
98
|
+
type: Injectable
|
|
99
|
+
}] });
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Content-type predicates for the file preview path. Shared by the document detail Tab and the
|
|
103
|
+
* standalone file-preview page (#277) so the two cannot drift on which content types render inline.
|
|
104
|
+
* Kept as pure functions (not signals) — each component still wraps them in its own computed over
|
|
105
|
+
* its own `document()` signal, but the classification rule itself lives in one place.
|
|
106
|
+
*/
|
|
107
|
+
function isImageContentType(contentType) {
|
|
108
|
+
return !!contentType && contentType.startsWith('image/');
|
|
109
|
+
}
|
|
110
|
+
function isPdfContentType(contentType) {
|
|
111
|
+
return contentType === 'application/pdf';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { DocumentFileBlobService as D, isPdfContentType as a, isImageContentType as i };
|
|
115
|
+
//# sourceMappingURL=dignite-vault-extract-documents-content-type-DjCs-s4E.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dignite-vault-extract-documents-content-type-DjCs-s4E.mjs","sources":["../../../packages/vault-extract/documents/src/lib/shared/document-file-blob.service.ts","../../../packages/vault-extract/documents/src/lib/shared/content-type.ts"],"sourcesContent":["import { DestroyRef, Injectable, OnDestroy, inject, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { DocumentService } from '@dignite/vault-extract';\n\n/**\n * Owns the original-file blob lifecycle for the document preview path (#277): fetch via\n * DocumentService.getBlob (Bearer token, never in the URL) → createObjectURL → sanitize for the\n * PDF iframe → revoke on destroy. Both the detail page's Original File tab and the standalone\n * file-preview page consume this single seam so the lifecycle can no longer drift across two\n * hand-copied implementations.\n *\n * NOT `providedIn: 'root'` — declared in each consuming component's `providers`, so one instance\n * lives per component and its `ngOnDestroy` revoke is scoped to that component's lifetime.\n */\n@Injectable()\nexport class DocumentFileBlobService implements OnDestroy {\n private readonly documentService = inject(DocumentService);\n private readonly sanitizer = inject(DomSanitizer);\n private readonly destroyRef = inject(DestroyRef);\n\n // raw blob: URL — used for <img> src, download anchors/trigger, and ngOnDestroy revoke.\n readonly blobUrl = signal<string | null>(null);\n // PDF iframe resource URL must pass through DomSanitizer or Angular drops it as unsafe\n // (self-minted same-origin blob:, safe to trust).\n readonly safeResourceUrl = signal<SafeResourceUrl | null>(null);\n readonly isLoading = signal(false);\n // Unified failure signal: blob download failed, or an <img>/iframe render failed (markError).\n readonly hasError = signal(false);\n\n /**\n * Lazy-load the blob once. Idempotent: short-circuits when already cached or a request is in\n * flight, so it is safe to call from a Tab activation, a Refresh, or a download click without\n * duplicate fetches. Observe outcome via the `blobUrl` / `isLoading` / `hasError` signals.\n */\n ensureLoaded(documentId: string): void {\n if (this.blobUrl() || this.isLoading()) return;\n this.isLoading.set(true);\n this.hasError.set(false);\n\n this.documentService\n .getBlob(documentId)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: blob => {\n const url = URL.createObjectURL(blob);\n this.blobUrl.set(url);\n this.safeResourceUrl.set(this.sanitizer.bypassSecurityTrustResourceUrl(url));\n this.isLoading.set(false);\n },\n error: () => {\n this.isLoading.set(false);\n this.hasError.set(true);\n },\n });\n }\n\n // Clear the error so an <img>/iframe rebuild retries; keeps any cached blob.\n resetError(): void {\n this.hasError.set(false);\n }\n\n // Drop the cached blob entirely and return to the pristine state. Used when the host component is reused\n // for a different document (the detail page reacts to :id route changes instead of being re-created), so\n // the previous document's blob never leaks into the new one's preview. Revokes the object URL — a fresh\n // ensureLoaded() will fetch and mint a new one.\n reset(): void {\n const url = this.blobUrl();\n if (url) URL.revokeObjectURL(url);\n this.blobUrl.set(null);\n this.safeResourceUrl.set(null);\n this.isLoading.set(false);\n this.hasError.set(false);\n }\n\n // Flag a render failure (e.g. <img> decode error after the blob downloaded fine).\n markError(): void {\n this.hasError.set(true);\n }\n\n // Trigger a browser download from the cached blob via a hidden <a download>. Does not revoke —\n // the same URL may still back an inline preview; ngOnDestroy owns the single revoke.\n download(fileName: string): void {\n const url = this.blobUrl();\n if (!url) return;\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = fileName;\n anchor.click();\n }\n\n ngOnDestroy(): void {\n const url = this.blobUrl();\n if (url) URL.revokeObjectURL(url);\n }\n}\n","/**\n * Content-type predicates for the file preview path. Shared by the document detail Tab and the\n * standalone file-preview page (#277) so the two cannot drift on which content types render inline.\n * Kept as pure functions (not signals) — each component still wraps them in its own computed over\n * its own `document()` signal, but the classification rule itself lives in one place.\n */\nexport function isImageContentType(contentType: string | null | undefined): boolean {\n return !!contentType && contentType.startsWith('image/');\n}\n\nexport function isPdfContentType(contentType: string | null | undefined): boolean {\n return contentType === 'application/pdf';\n}\n"],"names":[],"mappings":"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"}
|