@xiriframework/xiri-ng 0.3.2 → 0.4.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/fesm2022/xiriframework-xiri-ng.mjs +52 -14
- package/fesm2022/xiriframework-xiri-ng.mjs.map +1 -1
- package/package.json +1 -1
- package/skills/xiri-ng-expert/SKILL.md +3 -2
- package/skills/xiri-ng-expert/references/components.md +3 -1
- package/skills/xiri-ng-expert/references/setup.md +23 -4
- package/types/xiriframework-xiri-ng.d.ts +14 -2
- package/types/xiriframework-xiri-ng.d.ts.map +1 -1
|
@@ -2904,7 +2904,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
2904
2904
|
}], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }] } });
|
|
2905
2905
|
|
|
2906
2906
|
class XiriDownloadService {
|
|
2907
|
-
|
|
2907
|
+
/**
|
|
2908
|
+
* Opens an empty tab and returns its handle for a later download( …, tab ).
|
|
2909
|
+
*
|
|
2910
|
+
* MUST be called synchronously from the click handler: after an HTTP round trip the
|
|
2911
|
+
* transient user activation is gone and the browser blocks the popup. Returns null
|
|
2912
|
+
* when it was blocked anyway — pass that through, download() then saves the file.
|
|
2913
|
+
*/
|
|
2914
|
+
openTab() {
|
|
2915
|
+
return window.open('about:blank', '_blank');
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Turns the response body into a file. With a tab handle from openTab() the file is
|
|
2919
|
+
* displayed in that tab, otherwise it is saved via a download anchor.
|
|
2920
|
+
*/
|
|
2921
|
+
download(result, filename, tab) {
|
|
2908
2922
|
const contentDisposition = result.headers.get('content-disposition');
|
|
2909
2923
|
if (contentDisposition) {
|
|
2910
2924
|
filename = contentDisposition.split('filename=')[1];
|
|
@@ -2913,9 +2927,14 @@ class XiriDownloadService {
|
|
|
2913
2927
|
const contentType = result.headers.get('content-type') ?? undefined;
|
|
2914
2928
|
const file = new File([result.body], filename, { type: contentType });
|
|
2915
2929
|
const fileData = URL.createObjectURL(file);
|
|
2916
|
-
if (
|
|
2917
|
-
|
|
2918
|
-
|
|
2930
|
+
if (tab) {
|
|
2931
|
+
// ponytail: caps window.opener reach. A blob: document is same-origin anyway, so this is
|
|
2932
|
+
// no licence to display untrusted HTML — only a correct Content-Type keeps it a viewer.
|
|
2933
|
+
tab.opener = null;
|
|
2934
|
+
tab.location.replace(fileData);
|
|
2935
|
+
// ponytail: 60s is plenty to load; without it the blob lives until the page reloads.
|
|
2936
|
+
setTimeout(() => URL.revokeObjectURL(fileData), 60 * 1000);
|
|
2937
|
+
return true;
|
|
2919
2938
|
}
|
|
2920
2939
|
const a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
|
|
2921
2940
|
a.download = filename;
|
|
@@ -3162,7 +3181,7 @@ class XiriDialogComponent {
|
|
|
3162
3181
|
});
|
|
3163
3182
|
}, this.refreshTime);
|
|
3164
3183
|
}
|
|
3165
|
-
download(data) {
|
|
3184
|
+
download(data, tab) {
|
|
3166
3185
|
if (data === null) {
|
|
3167
3186
|
// TODO: fix, this is actually an external link
|
|
3168
3187
|
const file = window.open(this.dataService.getConfigApi() + this.url, 'Report');
|
|
@@ -3182,7 +3201,7 @@ class XiriDialogComponent {
|
|
|
3182
3201
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3183
3202
|
.subscribe({
|
|
3184
3203
|
next: (result) => {
|
|
3185
|
-
const ret = this.downloadService.download(result, 'Report',
|
|
3204
|
+
const ret = this.downloadService.download(result, 'Report', tab);
|
|
3186
3205
|
if (!ret)
|
|
3187
3206
|
this.buttons.set([{
|
|
3188
3207
|
text: 'Download',
|
|
@@ -3194,6 +3213,7 @@ class XiriDialogComponent {
|
|
|
3194
3213
|
else
|
|
3195
3214
|
this.dialogRef.close(this.extra);
|
|
3196
3215
|
}, error: (err) => {
|
|
3216
|
+
tab?.close();
|
|
3197
3217
|
this.snackbar.error(err.error?.error || 'Unknown Error');
|
|
3198
3218
|
}
|
|
3199
3219
|
});
|
|
@@ -3217,7 +3237,10 @@ class XiriDialogComponent {
|
|
|
3217
3237
|
this.startSend(button.data);
|
|
3218
3238
|
}
|
|
3219
3239
|
else if (button.action == 'download') {
|
|
3220
|
-
this.
|
|
3240
|
+
const data = this.type() == 'form' ? this.formValues : null;
|
|
3241
|
+
// Only the blob path needs a tab handle; data === null is an external link that
|
|
3242
|
+
// opens its own tab. openTab() must run here, inside the click.
|
|
3243
|
+
this.download(data, data !== null && button.target === '_blank' ? this.downloadService.openTab() : null);
|
|
3221
3244
|
}
|
|
3222
3245
|
else if (button.action == 'link') {
|
|
3223
3246
|
this.router.navigate([button.url]).then();
|
|
@@ -3545,6 +3568,9 @@ class XiriButtonComponent {
|
|
|
3545
3568
|
actionDownload() {
|
|
3546
3569
|
const data = { ...this.filterData(), ...this.button().data };
|
|
3547
3570
|
this.loading.set(true);
|
|
3571
|
+
// Must happen before the request: openTab() needs the click's user activation.
|
|
3572
|
+
// Without one (e.g. autoLoad) it returns null and the file is saved instead.
|
|
3573
|
+
const tab = this.button().target === '_blank' ? this.downloadService.openTab() : null;
|
|
3548
3574
|
this.dataService.postFileResponse(this._url() ?? '', data)
|
|
3549
3575
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3550
3576
|
.subscribe({
|
|
@@ -3554,9 +3580,11 @@ class XiriButtonComponent {
|
|
|
3554
3580
|
if (btn.filename != undefined)
|
|
3555
3581
|
filename = btn.filename;
|
|
3556
3582
|
else
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3583
|
+
// ponytail: no extension guess when the file is displayed — the
|
|
3584
|
+
// viewer would offer "Report.csv" for a PDF on save.
|
|
3585
|
+
filename = (btn.text != undefined ? btn.text : 'Download')
|
|
3586
|
+
+ (tab ? '' : '.csv');
|
|
3587
|
+
this.downloadService.download(result, filename, tab);
|
|
3560
3588
|
this.result.emit({
|
|
3561
3589
|
button: this.button(),
|
|
3562
3590
|
result: result,
|
|
@@ -3567,6 +3595,7 @@ class XiriButtonComponent {
|
|
|
3567
3595
|
},
|
|
3568
3596
|
error: (err) => {
|
|
3569
3597
|
console.log("xiri-button download error", err);
|
|
3598
|
+
tab?.close();
|
|
3570
3599
|
this.result.emit({
|
|
3571
3600
|
button: this.button(),
|
|
3572
3601
|
result: null,
|
|
@@ -4906,12 +4935,18 @@ class XiriTableComponent {
|
|
|
4906
4935
|
});
|
|
4907
4936
|
break;
|
|
4908
4937
|
}
|
|
4909
|
-
case 'download':
|
|
4938
|
+
case 'download': {
|
|
4939
|
+
// Before the request — openTab() needs the click's user activation.
|
|
4940
|
+
const tab = button.target === '_blank' ? this.downloadService.openTab() : null;
|
|
4910
4941
|
this.dataService.postFileResponse(button.url ?? '', payload).pipe(takeUntil(this.reloadAbort$)).subscribe({
|
|
4911
|
-
next: (result) => this.downloadService.download(result, button.filename || 'download.csv',
|
|
4912
|
-
error: (err) =>
|
|
4942
|
+
next: (result) => this.downloadService.download(result, button.filename || 'download.csv', tab),
|
|
4943
|
+
error: (err) => {
|
|
4944
|
+
tab?.close();
|
|
4945
|
+
this.snackbar.error(errorMessage(err) || 'Download Error');
|
|
4946
|
+
},
|
|
4913
4947
|
});
|
|
4914
4948
|
break;
|
|
4949
|
+
}
|
|
4915
4950
|
default:
|
|
4916
4951
|
this.dataService.post(button.url ?? '', payload).pipe(takeUntil(this.reloadAbort$)).subscribe({
|
|
4917
4952
|
next: (result) => {
|
|
@@ -5003,11 +5038,14 @@ class XiriTableComponent {
|
|
|
5003
5038
|
if (this.selection.isEmpty())
|
|
5004
5039
|
return;
|
|
5005
5040
|
const url = button.url ? button.url : '';
|
|
5041
|
+
// Before the request — openTab() needs the click's user activation.
|
|
5042
|
+
const tab = button.target === '_blank' ? this.downloadService.openTab() : null;
|
|
5006
5043
|
this.dataService.postFileResponse(url, this.getSelectionIDs()).pipe(takeUntil(this.reloadAbort$)).subscribe({
|
|
5007
5044
|
next: (result) => {
|
|
5008
|
-
this.downloadService.download(result, 'download.csv',
|
|
5045
|
+
this.downloadService.download(result, 'download.csv', tab);
|
|
5009
5046
|
},
|
|
5010
5047
|
error: (err) => {
|
|
5048
|
+
tab?.close();
|
|
5011
5049
|
this.snackbar.error(errorMessage(err) || 'Download Error');
|
|
5012
5050
|
}
|
|
5013
5051
|
});
|