@firestitch/report 18.3.1 → 18.3.2
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/app/reports/data/report.data.d.ts +1 -0
- package/app/reports/interfaces/report.interface.d.ts +3 -4
- package/app/reports/views/reports/fs-ai-reports.component.d.ts +4 -2
- package/esm2022/app/reports/data/report.data.mjs +14 -4
- package/esm2022/app/reports/interfaces/report.interface.mjs +5 -9
- package/esm2022/app/reports/views/reports/fs-ai-reports.component.mjs +177 -76
- package/fesm2022/{firestitch-report-echarts-Dg5PjbjT.mjs → firestitch-report-echarts-D4xJt3zs.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-Dg5PjbjT.mjs.map → firestitch-report-echarts-D4xJt3zs.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-JORFp_xa.mjs → firestitch-report-firestitch-report-DY8HwvAU.mjs} +199 -94
- package/fesm2022/firestitch-report-firestitch-report-DY8HwvAU.mjs.map +1 -0
- package/fesm2022/firestitch-report.mjs +1 -1
- package/package.json +1 -1
- package/fesm2022/firestitch-report-firestitch-report-JORFp_xa.mjs.map +0 -1
|
@@ -2,10 +2,10 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, Injectable, Component, ChangeDetectionStrategy, Input, signal, DestroyRef, ViewChild, input, output, computed, viewChild, ElementRef, effect, untracked, EventEmitter, ChangeDetectorRef, NgZone, Output, HostBinding, ViewChildren, CUSTOM_ELEMENTS_SCHEMA, model } from '@angular/core';
|
|
3
3
|
import * as i1$1 from '@angular/forms';
|
|
4
4
|
import { FormsModule, ControlContainer, NgForm } from '@angular/forms';
|
|
5
|
-
import * as i3$
|
|
5
|
+
import * as i3$3 from '@angular/material/button';
|
|
6
6
|
import { MatIconButton, MatButton, MatButtonModule } from '@angular/material/button';
|
|
7
7
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatDialog } from '@angular/material/dialog';
|
|
8
|
-
import * as
|
|
8
|
+
import * as i5$2 from '@angular/material/icon';
|
|
9
9
|
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
|
10
10
|
import * as i2$5 from '@angular/material/tooltip';
|
|
11
11
|
import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
|
|
@@ -57,12 +57,12 @@ import { FsDatePickerModule } from '@firestitch/datepicker';
|
|
|
57
57
|
import { MatOption } from '@angular/material/core';
|
|
58
58
|
import { guid } from '@firestitch/common';
|
|
59
59
|
import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|
60
|
+
import * as i1$4 from '@firestitch/chip';
|
|
61
|
+
import { FsChipModule } from '@firestitch/chip';
|
|
60
62
|
import * as i4 from '@firestitch/skeleton';
|
|
61
63
|
import { FsSkeletonModule } from '@firestitch/skeleton';
|
|
62
|
-
import * as
|
|
64
|
+
import * as i4$1 from '@firestitch/tree';
|
|
63
65
|
import { FsTreeComponent, TreeDragMode, TreeActionType, FsTreeModule } from '@firestitch/tree';
|
|
64
|
-
import * as i1$4 from '@firestitch/chip';
|
|
65
|
-
import { FsChipModule } from '@firestitch/chip';
|
|
66
66
|
import * as i2$4 from '@firestitch/date';
|
|
67
67
|
import { FsDateModule } from '@firestitch/date';
|
|
68
68
|
import { FsMarkdownRendererComponent, FsMarkdownEditorComponent } from '@firestitch/markdown-editor';
|
|
@@ -123,14 +123,23 @@ class ReportData {
|
|
|
123
123
|
...config,
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
|
-
//
|
|
127
|
-
//
|
|
126
|
+
// Soft-delete, whatever the folder holds: its contents leave the listing with
|
|
127
|
+
// it, and undeleteFolder() brings the lot back.
|
|
128
128
|
deleteFolder(reportFolderId, config = {}) {
|
|
129
129
|
return this._api.delete(this._path(`folders/${reportFolderId}`), {}, {
|
|
130
130
|
key: 'folder',
|
|
131
131
|
...config,
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
+
// Restore a soft-deleted folder through its own action endpoint — the folder
|
|
135
|
+
// PUT does not accept `state`. Any deleted folder above it is restored too, so
|
|
136
|
+
// it lands back where it was.
|
|
137
|
+
undeleteFolder(reportFolderId, config = {}) {
|
|
138
|
+
return this._api.post(this._path(`folders/${reportFolderId}/undelete`), {}, {
|
|
139
|
+
key: 'folder',
|
|
140
|
+
...config,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
134
143
|
rename(reportId, name, config = {}) {
|
|
135
144
|
return this._api.put(this._path(`${reportId}`), { name }, {
|
|
136
145
|
key: 'report',
|
|
@@ -216,7 +225,8 @@ class ReportData {
|
|
|
216
225
|
});
|
|
217
226
|
}
|
|
218
227
|
// Restore a soft-deleted report through its own action endpoint — the report
|
|
219
|
-
// PUT does not accept `state`.
|
|
228
|
+
// PUT does not accept `state`. Its folder is restored too when it is deleted,
|
|
229
|
+
// or inside one that is, so the report lands back where it was.
|
|
220
230
|
undelete(reportId, config = {}) {
|
|
221
231
|
return this._api.post(this._path(`${reportId}/undelete`), {}, {
|
|
222
232
|
key: 'report',
|
|
@@ -493,19 +503,15 @@ var ReportNodeType;
|
|
|
493
503
|
ReportNodeType["Folder"] = "folder";
|
|
494
504
|
ReportNodeType["Report"] = "report";
|
|
495
505
|
})(ReportNodeType || (ReportNodeType = {}));
|
|
496
|
-
// Reports are soft-deletable
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
506
|
+
// Reports and folders are both soft-deletable. A deleted report keeps its pages
|
|
507
|
+
// and components; a deleted folder keeps everything filed under it, which leaves
|
|
508
|
+
// the listing with it and comes back with it. "Show Deleted" shows the whole
|
|
509
|
+
// tree, live and deleted together, so any of it can be restored in place.
|
|
500
510
|
var ReportState;
|
|
501
511
|
(function (ReportState) {
|
|
502
512
|
ReportState["Active"] = "active";
|
|
503
513
|
ReportState["Deleted"] = "deleted";
|
|
504
514
|
})(ReportState || (ReportState = {}));
|
|
505
|
-
const ReportStates = [
|
|
506
|
-
{ name: 'Active', value: ReportState.Active },
|
|
507
|
-
{ name: 'Deleted', value: ReportState.Deleted },
|
|
508
|
-
];
|
|
509
515
|
|
|
510
516
|
// A date-range boundary as the picked LOCAL calendar day (no time, no tz).
|
|
511
517
|
// Date-range filters are calendar-day semantics, so the boundary must carry
|
|
@@ -3882,7 +3888,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3882
3888
|
// complete synchronously after setOption.
|
|
3883
3889
|
async function renderChartImage(component, data, width, height, pixelRatio = 3) {
|
|
3884
3890
|
// The same tree-shaken echarts build + 'report' theme the live canvas uses.
|
|
3885
|
-
const echarts = (await import('./firestitch-report-echarts-
|
|
3891
|
+
const echarts = (await import('./firestitch-report-echarts-D4xJt3zs.mjs')).default;
|
|
3886
3892
|
const host = document.createElement('div');
|
|
3887
3893
|
host.style.width = `${width}px`;
|
|
3888
3894
|
host.style.height = `${height}px`;
|
|
@@ -4970,7 +4976,7 @@ class ReportComponent {
|
|
|
4970
4976
|
ReportExportService,
|
|
4971
4977
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
4972
4978
|
// this route's chunk.
|
|
4973
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4979
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D4xJt3zs.mjs').then((module) => module.default) }),
|
|
4974
4980
|
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }, { propertyName: "_canvas", first: true, predicate: ReportCanvasComponent, descendants: true }], ngImport: i0, template: "<div\n #split\n class=\"report fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n style=\"min-height: 500px;\"\n basePath=\"reports\"\n [requestData]=\"{ reportId: selected?.id ?? null }\"\n [introMessage]=\"introMessage\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n <div class=\"viewer fs-flex fs-column\">\n <div class=\"fs-row.align-center.gap-sm\">\n <fs-autocomplete-chips\n class=\"fs-flex\"\n [fetch]=\"fetchReports\"\n [(ngModel)]=\"selected\"\n [disabled]=\"loadingReports\"\n [padless]=\"true\"\n [multiple]=\"false\"\n [fetchOnFocus]=\"true\"\n (ngModelChange)=\"reportChange($event)\"\n placeholder=\"Report\"\n name=\"report\">\n <ng-template\n fsAutocompleteChipsTemplate\n let-object=\"object\">\n {{ object.name }}\n </ng-template>\n <ng-template\n fsAutocompleteChipsStatic\n (click)=\"createReport()\">\n Create Report\n </ng-template>\n </fs-autocomplete-chips>\n @if (report && editMode) {\n <!-- What layout editing can add or switch to, and the way out of it \u2014\n the same outlined buttons as the fs-ai-report toolbar. -->\n <button\n mat-stroked-button\n type=\"button\"\n matTooltip=\"Add a block of text\"\n (click)=\"addText()\">\n <mat-icon>title</mat-icon>\n Add text\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"master-toggle\"\n [class.active]=\"isMasterMode\"\n [matTooltip]=\"isMasterMode ? 'Back to editing this page' : 'Edit the header and footer shown on every page'\"\n (click)=\"toggleMasterMode()\">\n <mat-icon>web_asset</mat-icon>\n Header & footer\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"toggleEditMode()\">\n <mat-icon>check</mat-icon>\n Done editing\n </button>\n }\n @if (report) {\n <fs-menu>\n <ng-template\n fs-menu-item\n (click)=\"reportSettings()\">\n <mat-icon>\n tune\n </mat-icon>\n Report settings\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"toggleEditMode()\"\n [disabled]=\"editMode\">\n <mat-icon>\n dashboard_customize\n </mat-icon>\n {{ editMode ? 'Done editing layout' : 'Edit layout' }}\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n <mat-icon>\n slideshow\n </mat-icon>\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n <mat-icon>\n picture_as_pdf\n </mat-icon>\n Export PDF\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportData()\">\n <mat-icon>\n table_view\n </mat-icon>\n Export Raw Data\n </ng-template>\n @if ((report?.pages?.length ?? 0) > 1) {\n <ng-template\n fs-menu-item\n (click)=\"exportData(true)\">\n <mat-icon>\n table_view\n </mat-icon>\n Export Raw Data (This Page)\n </ng-template>\n }\n </fs-menu>\n }\n </div>\n @if (report) {\n <!-- Report-level filters only (the report's actions live in the menu\n above). fs-filter reads its config once at init, so it's keyed on the\n filter signature: when the report-level filter set changes the block\n is recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter [config]=\"reportFilterConfig\"></fs-filter>\n }\n }\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\">\n </app-report-canvas>\n }\n </div>\n</div>", styles: [".report{height:100%;min-height:500px}.report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:500px;min-width:0;border:none}.report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.report .viewer fs-filter{flex:0 0 auto;margin-top:10px;margin-bottom:0}.report .viewer ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.report .resizer:before{content:\"\";width:0px;background:#0000001f;transition:width .12s ease,background-color .12s ease}.report .resizer:hover:before{width:3px;background:var(--brand-primary-color)}.report.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.report.resizing .resizer:before{width:3px;background:var(--brand-primary-color)}.report.resizing .chat,.report.resizing .viewer{pointer-events:none}::ng-deep body.body-report-reports .mat-mdc-card-content{display:flex;flex-direction:column;box-sizing:border-box}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel{flex:1;min-height:0}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel router-outlet+ng-component{height:100%}.master-toggle.active{background:#eceff3}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsAutocompleteChipsModule }, { kind: "component", type: i2$3.FsAutocompleteChipsComponent, selector: "fs-autocomplete-chips", inputs: ["fetch", "appearance", "floatLabel", "readonly", "size", "label", "placeholder", "chipImage", "chipBackground", "chipColor", "chipIcon", "chipIconColor", "chipClass", "chipPadding", "shape", "hint", "allowText", "allowObject", "delay", "minPanelWidth", "maxPanelHeight", "validateText", "removable", "allowClear", "color", "background", "orderable", "padless", "initOnClick", "fetchOnFocus", "multiple", "multipleAdd", "confirm", "disabled", "groupBy", "panelWidth", "panelClass", "compareWith"], outputs: ["selected", "removed", "reordered", "clear", "panelOpened", "panelClosed"] }, { kind: "directive", type: i2$3.FsAutocompleteObjectDirective, selector: "[fsAutocompleteObject],[fsAutocompleteChipsTemplate]" }, { kind: "directive", type: i2$3.FsAutocompleteChipsStaticDirective, selector: "[fsAutocompleteChipsStatic]", inputs: ["show", "disable"], outputs: ["click", "selected"] }, { kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$1.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$1.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "ready", "introMessage", "showThinking"], outputs: ["response", "showThinkingChange"] }, { kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ReportCanvasComponent, selector: "app-report-canvas", inputs: ["report", "editMode", "pageId"], outputs: ["componentSettings", "reportChanged", "pageChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4975
4981
|
}
|
|
4976
4982
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportComponent, decorators: [{
|
|
@@ -4985,7 +4991,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4985
4991
|
ReportExportService,
|
|
4986
4992
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
4987
4993
|
// this route's chunk.
|
|
4988
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4994
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D4xJt3zs.mjs').then((module) => module.default) }),
|
|
4989
4995
|
], imports: [
|
|
4990
4996
|
FormsModule,
|
|
4991
4997
|
FsAutocompleteChipsModule,
|
|
@@ -5662,17 +5668,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
5662
5668
|
// report in no folder is a root node, which is where every report sat before
|
|
5663
5669
|
// folders existed, so nothing had to be migrated or bucketed as "uncategorized".
|
|
5664
5670
|
//
|
|
5665
|
-
//
|
|
5666
|
-
//
|
|
5667
|
-
//
|
|
5668
|
-
//
|
|
5669
|
-
//
|
|
5670
|
-
//
|
|
5671
|
-
//
|
|
5671
|
+
// Folders and reports are both soft-deletable, and a folder deletes whatever it
|
|
5672
|
+
// holds. Only the folder's own row is written: everything under it drops out of
|
|
5673
|
+
// the listing because it sits under a deleted folder, and comes back with it.
|
|
5674
|
+
// "Show Deleted" shows the WHOLE tree — live and deleted together, each deleted
|
|
5675
|
+
// row where it was filed — and on anything deleted the kebab offers Restore
|
|
5676
|
+
// instead. Restoring also restores any deleted folder above the row, so it lands
|
|
5677
|
+
// back where it was rather than at the root.
|
|
5678
|
+
//
|
|
5679
|
+
// Every folder starts collapsed; the tree remembers what was opened across its
|
|
5680
|
+
// own reloads (a rename, a move, the Show Deleted toggle) and across a search.
|
|
5672
5681
|
class FsAiReportsComponent {
|
|
5673
|
-
// How deep the tree opens on load. Folders are audience buckets, not a
|
|
5674
|
-
// filesystem, so there is nothing worth hiding behind a closed chevron.
|
|
5675
|
-
static EXPAND_LEVEL = 10;
|
|
5676
5682
|
// API base for the reports endpoints; override to mount elsewhere.
|
|
5677
5683
|
basePath = input('reports');
|
|
5678
5684
|
// Where a row links to; ':id' is replaced with the report id. Default takes
|
|
@@ -5696,6 +5702,10 @@ class FsAiReportsComponent {
|
|
|
5696
5702
|
_destroyRef = inject(DestroyRef);
|
|
5697
5703
|
// The filter bar's current query — carries `state` when Show Deleted is on.
|
|
5698
5704
|
_query = {};
|
|
5705
|
+
// The folders that were open when a search started; null while no search is
|
|
5706
|
+
// running. The search opens every match's path over the top of them, and
|
|
5707
|
+
// clearing it puts these back.
|
|
5708
|
+
_expandedBeforeSearch = null;
|
|
5699
5709
|
constructor() {
|
|
5700
5710
|
effect(() => {
|
|
5701
5711
|
this._reportData.basePath = this.basePath();
|
|
@@ -5713,11 +5723,6 @@ class FsAiReportsComponent {
|
|
|
5713
5723
|
openLink(report) {
|
|
5714
5724
|
return this._patternCommands(this.openPattern(), report.id);
|
|
5715
5725
|
}
|
|
5716
|
-
// A deleted report can't be opened — the backend only assembles active ones —
|
|
5717
|
-
// so in Show Deleted mode its name renders as plain text rather than a link.
|
|
5718
|
-
isDeleted(report) {
|
|
5719
|
-
return report?.state === ReportState.Deleted;
|
|
5720
|
-
}
|
|
5721
5726
|
// Prompt for a title, create the report — filed under reportFolderId when a
|
|
5722
5727
|
// folder's kebab asked, at the root otherwise — then route to it (createPattern).
|
|
5723
5728
|
createReport(reportFolderId = null) {
|
|
@@ -5752,13 +5757,16 @@ class FsAiReportsComponent {
|
|
|
5752
5757
|
type: ItemType.Keyword,
|
|
5753
5758
|
label: 'Search',
|
|
5754
5759
|
},
|
|
5760
|
+
// A checkbox rather than the usual Status select with a "Show Deleted"
|
|
5761
|
+
// isolate. Isolating the deleted rows would strand them: a deleted
|
|
5762
|
+
// report in a live folder arrives without the folder it sits in, and a
|
|
5763
|
+
// tree cannot place a node whose parent it never received. So the
|
|
5764
|
+
// toggle asks for everything, and the rows mark what is deleted.
|
|
5755
5765
|
{
|
|
5756
5766
|
name: 'state',
|
|
5757
|
-
type: ItemType.
|
|
5758
|
-
label: '
|
|
5759
|
-
|
|
5760
|
-
values: () => ReportStates,
|
|
5761
|
-
isolate: { label: 'Show Deleted', value: ReportState.Deleted },
|
|
5767
|
+
type: ItemType.Checkbox,
|
|
5768
|
+
label: 'Show Deleted',
|
|
5769
|
+
checked: [ReportState.Active, ReportState.Deleted].join(','),
|
|
5762
5770
|
},
|
|
5763
5771
|
],
|
|
5764
5772
|
actions: [
|
|
@@ -5791,15 +5799,19 @@ class FsAiReportsComponent {
|
|
|
5791
5799
|
click: () => this.createReport(),
|
|
5792
5800
|
},
|
|
5793
5801
|
],
|
|
5794
|
-
// The two items are answered in different places and
|
|
5795
|
-
// The keyword drives the tree's own search, which is the whole
|
|
5796
|
-
// searching a tree — it reveals a match nested inside collapsed
|
|
5797
|
-
// rather than only filtering what is already on screen. `state` is
|
|
5798
|
-
// Show Deleted toggle, and only the server can answer that.
|
|
5802
|
+
// The two items are answered in different places, and each only when it
|
|
5803
|
+
// changed. The keyword drives the tree's own search, which is the whole
|
|
5804
|
+
// point of searching a tree — it reveals a match nested inside collapsed
|
|
5805
|
+
// folders rather than only filtering what is already on screen. `state` is
|
|
5806
|
+
// the Show Deleted toggle, and only the server can answer that. Running the
|
|
5807
|
+
// search on a toggle would clear it, and clearing collapses the tree.
|
|
5799
5808
|
change: (query) => {
|
|
5809
|
+
const keywordChanged = String(query?.keyword ?? '') !== String(this._query.keyword ?? '');
|
|
5800
5810
|
const stateChanged = String(query?.state ?? '') !== String(this._query.state ?? '');
|
|
5801
5811
|
this._query = { ...query };
|
|
5802
|
-
|
|
5812
|
+
if (keywordChanged) {
|
|
5813
|
+
this._search(query?.keyword);
|
|
5814
|
+
}
|
|
5803
5815
|
if (stateChanged) {
|
|
5804
5816
|
this._load();
|
|
5805
5817
|
}
|
|
@@ -5807,22 +5819,26 @@ class FsAiReportsComponent {
|
|
|
5807
5819
|
};
|
|
5808
5820
|
}
|
|
5809
5821
|
_initTreeConfig() {
|
|
5822
|
+
// No expandLevel: every folder starts collapsed.
|
|
5810
5823
|
this.treeConfig = {
|
|
5811
5824
|
data: [],
|
|
5812
5825
|
childrenName: 'children',
|
|
5813
|
-
expandLevel: FsAiReportsComponent.EXPAND_LEVEL,
|
|
5814
5826
|
draggable: true,
|
|
5815
5827
|
// The whole row is the grip and the handle stays hidden; a press only
|
|
5816
5828
|
// becomes a drag once the pointer moves, so the kebab, the chevron and the
|
|
5817
5829
|
// report link all stay clickable.
|
|
5818
5830
|
dragMode: TreeDragMode.Node,
|
|
5831
|
+
// Anything deleted is restored before it is moved — the backend refuses
|
|
5832
|
+
// to edit a deleted row, and a drag would only snap back.
|
|
5833
|
+
canDrag: (node) => !this._isDeletedNode(node),
|
|
5819
5834
|
// canDrop is asked about the node being DRAGGED, so only `toParent`
|
|
5820
5835
|
// describes the destination — reading node.level/node.parent here would
|
|
5821
5836
|
// describe where it came from. A report can never be a parent because its
|
|
5822
5837
|
// type says so, and that single test is the whole "folders nest, reports
|
|
5823
|
-
// are leaves" rule on this side.
|
|
5838
|
+
// are leaves" rule on this side. A deleted folder is no destination
|
|
5839
|
+
// either: whatever landed in it would vanish from the live listing.
|
|
5824
5840
|
canDrop: (node, fromParent, toParent) => {
|
|
5825
|
-
return !toParent || toParent
|
|
5841
|
+
return !toParent || (this._isFolderNode(toParent) && !this._isDeletedNode(toParent));
|
|
5826
5842
|
},
|
|
5827
5843
|
changeReorder: (event) => this._move(event),
|
|
5828
5844
|
nodeClass: (node) => node.data?.type,
|
|
@@ -5846,12 +5862,12 @@ class FsAiReportsComponent {
|
|
|
5846
5862
|
},
|
|
5847
5863
|
{
|
|
5848
5864
|
label: 'New report',
|
|
5849
|
-
show: (node) => this._isFolderNode(node),
|
|
5865
|
+
show: (node) => this._isFolderNode(node) && !this._isDeletedNode(node),
|
|
5850
5866
|
click: (node) => this.createReport(node.data?.id),
|
|
5851
5867
|
},
|
|
5852
5868
|
{
|
|
5853
5869
|
label: 'New folder',
|
|
5854
|
-
show: (node) => this._isFolderNode(node),
|
|
5870
|
+
show: (node) => this._isFolderNode(node) && !this._isDeletedNode(node),
|
|
5855
5871
|
click: (node) => this._createFolder(node.data?.id),
|
|
5856
5872
|
},
|
|
5857
5873
|
{
|
|
@@ -5859,8 +5875,8 @@ class FsAiReportsComponent {
|
|
|
5859
5875
|
show: (node) => !this._isDeletedNode(node),
|
|
5860
5876
|
click: (node) => this._delete(node),
|
|
5861
5877
|
},
|
|
5862
|
-
//
|
|
5863
|
-
//
|
|
5878
|
+
// On every deleted row, including a live one that is only hidden by
|
|
5879
|
+
// a deleted folder above it — restoring that brings the folder back.
|
|
5864
5880
|
{
|
|
5865
5881
|
label: 'Restore',
|
|
5866
5882
|
show: (node) => this._isDeletedNode(node),
|
|
@@ -5875,66 +5891,128 @@ class FsAiReportsComponent {
|
|
|
5875
5891
|
return node?.data?.type === ReportNodeType.Folder;
|
|
5876
5892
|
}
|
|
5877
5893
|
_isDeletedNode(node) {
|
|
5878
|
-
return
|
|
5894
|
+
return !!node?.data?.deleted;
|
|
5879
5895
|
}
|
|
5880
5896
|
// Drive the tree's built-in search: it collapses everything, hides every node,
|
|
5881
5897
|
// then unhides each match and walks its ancestors open, so a report several
|
|
5882
|
-
// folders deep surfaces with its path expanded. Clearing
|
|
5883
|
-
//
|
|
5884
|
-
// left fully collapsed the moment somebody empties the search box.
|
|
5898
|
+
// folders deep surfaces with its path expanded. Clearing collapses everything
|
|
5899
|
+
// again, so the folders that were open before the search are put back.
|
|
5885
5900
|
_search(keyword) {
|
|
5886
5901
|
const tree = this._tree();
|
|
5887
5902
|
if (!tree) {
|
|
5888
5903
|
return;
|
|
5889
5904
|
}
|
|
5890
5905
|
if (keyword) {
|
|
5906
|
+
this._expandedBeforeSearch ??= this._expandedKeys();
|
|
5891
5907
|
tree.search(keyword);
|
|
5892
5908
|
return;
|
|
5893
5909
|
}
|
|
5894
5910
|
tree.clearSearch();
|
|
5895
|
-
|
|
5911
|
+
this._expandKeys(this._expandedBeforeSearch ?? []);
|
|
5912
|
+
this._expandedBeforeSearch = null;
|
|
5896
5913
|
}
|
|
5897
5914
|
_load() {
|
|
5898
|
-
|
|
5915
|
+
// Only the Show Deleted toggle is the server's to answer — the keyword is
|
|
5916
|
+
// the tree's own search — and an unchecked toggle sends nothing, leaving the
|
|
5917
|
+
// backend's active-only default in charge.
|
|
5918
|
+
const query = this._query.state ? { state: this._query.state } : {};
|
|
5919
|
+
this._reportData.tree(query)
|
|
5899
5920
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
5900
|
-
.subscribe((
|
|
5901
|
-
const nodes = this._buildNodes(
|
|
5921
|
+
.subscribe((reportTree) => {
|
|
5922
|
+
const nodes = this._buildNodes(reportTree);
|
|
5902
5923
|
this.nodes.set(nodes);
|
|
5903
5924
|
// The tree reads `config` once, in its own ngOnInit. The first load
|
|
5904
5925
|
// lands before it exists, so the data has to arrive through the config;
|
|
5905
5926
|
// every later one has to go through the instance or nothing changes.
|
|
5906
5927
|
this.treeConfig = { ...this.treeConfig, data: nodes };
|
|
5907
|
-
this._tree()
|
|
5928
|
+
const tree = this._tree();
|
|
5929
|
+
if (!tree) {
|
|
5930
|
+
return;
|
|
5931
|
+
}
|
|
5932
|
+
// setData() rebuilds every node collapsed, so a rename or a move would
|
|
5933
|
+
// otherwise slam shut every folder somebody had opened. Keys are stable
|
|
5934
|
+
// across loads ('f:12'), so what was open is reopened by key — and a
|
|
5935
|
+
// folder that no longer comes back simply is not.
|
|
5936
|
+
const expandedKeys = this._expandedKeys();
|
|
5937
|
+
tree.setData(nodes);
|
|
5938
|
+
this._expandKeys(expandedKeys);
|
|
5939
|
+
const keyword = String(this._query.keyword ?? '');
|
|
5940
|
+
if (keyword) {
|
|
5941
|
+
tree.search(keyword);
|
|
5942
|
+
}
|
|
5908
5943
|
});
|
|
5909
5944
|
}
|
|
5945
|
+
// The keys of every open folder, walked through the tree's public node API.
|
|
5946
|
+
_expandedKeys() {
|
|
5947
|
+
const tree = this._tree();
|
|
5948
|
+
if (!tree) {
|
|
5949
|
+
return [];
|
|
5950
|
+
}
|
|
5951
|
+
const walk = (parent) => tree.getNodes(parent)
|
|
5952
|
+
.filter((node) => node.isExpanded())
|
|
5953
|
+
.flatMap((node) => [node.data?.key, ...walk(node)]);
|
|
5954
|
+
return walk(null);
|
|
5955
|
+
}
|
|
5956
|
+
_expandKeys(keys) {
|
|
5957
|
+
const tree = this._tree();
|
|
5958
|
+
if (!tree || !keys.length) {
|
|
5959
|
+
return;
|
|
5960
|
+
}
|
|
5961
|
+
const open = new Set(keys);
|
|
5962
|
+
const walk = (parent) => {
|
|
5963
|
+
tree.getNodes(parent)
|
|
5964
|
+
.filter((node) => open.has(node.data?.key))
|
|
5965
|
+
.forEach((node) => {
|
|
5966
|
+
node.expand();
|
|
5967
|
+
walk(node);
|
|
5968
|
+
});
|
|
5969
|
+
};
|
|
5970
|
+
walk(null);
|
|
5971
|
+
}
|
|
5910
5972
|
// Flat API rows → the nested shape fs-tree renders. Folders come before
|
|
5911
5973
|
// reports at every level, each already name-ordered by the backend.
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5974
|
+
//
|
|
5975
|
+
// `deleted` is inherited downward: a deleted folder hides everything below it
|
|
5976
|
+
// without the rows below being deleted themselves, and that is only visible
|
|
5977
|
+
// from here, where the parent is in hand. The backend applies the same rule
|
|
5978
|
+
// when it filters (ReportFolderModel::getDeletedFolderIds()).
|
|
5979
|
+
_buildNodes(reportTree) {
|
|
5980
|
+
const folders = reportTree?.folders ?? [];
|
|
5981
|
+
const reports = reportTree?.reports ?? [];
|
|
5982
|
+
const build = (parentId, parentDeleted) => {
|
|
5916
5983
|
const folderNodes = folders
|
|
5917
5984
|
.filter((folder) => (folder.parentReportFolderId ?? null) === parentId)
|
|
5918
|
-
.map((folder) =>
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5985
|
+
.map((folder) => {
|
|
5986
|
+
const stateDeleted = folder.state === ReportState.Deleted;
|
|
5987
|
+
const deleted = parentDeleted || stateDeleted;
|
|
5988
|
+
return {
|
|
5989
|
+
key: `f:${folder.id}`,
|
|
5990
|
+
id: folder.id,
|
|
5991
|
+
name: folder.name,
|
|
5992
|
+
type: ReportNodeType.Folder,
|
|
5993
|
+
children: build(folder.id, deleted),
|
|
5994
|
+
stateDeleted,
|
|
5995
|
+
deleted,
|
|
5996
|
+
};
|
|
5997
|
+
});
|
|
5925
5998
|
const reportNodes = reports
|
|
5926
5999
|
.filter((report) => (report.reportFolderId ?? null) === parentId)
|
|
5927
|
-
.map((report) =>
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
6000
|
+
.map((report) => {
|
|
6001
|
+
const stateDeleted = report.state === ReportState.Deleted;
|
|
6002
|
+
return {
|
|
6003
|
+
key: `r:${report.id}`,
|
|
6004
|
+
id: report.id,
|
|
6005
|
+
name: report.name,
|
|
6006
|
+
type: ReportNodeType.Report,
|
|
6007
|
+
children: [],
|
|
6008
|
+
report,
|
|
6009
|
+
stateDeleted,
|
|
6010
|
+
deleted: parentDeleted || stateDeleted,
|
|
6011
|
+
};
|
|
6012
|
+
});
|
|
5935
6013
|
return [...folderNodes, ...reportNodes];
|
|
5936
6014
|
};
|
|
5937
|
-
return build(null);
|
|
6015
|
+
return build(null, false);
|
|
5938
6016
|
}
|
|
5939
6017
|
// A drop moved a node under a new parent, or out to the root. The tree has
|
|
5940
6018
|
// already moved it on screen, so both outcomes reload: a success to pick up
|
|
@@ -5995,9 +6073,9 @@ class FsAiReportsComponent {
|
|
|
5995
6073
|
this._load();
|
|
5996
6074
|
});
|
|
5997
6075
|
}
|
|
5998
|
-
//
|
|
5999
|
-
//
|
|
6000
|
-
//
|
|
6076
|
+
// A folder deletes with everything in it, so the confirm says what that is —
|
|
6077
|
+
// and that it all comes back from Show Deleted, which is what makes it safe to
|
|
6078
|
+
// offer.
|
|
6001
6079
|
_delete(node) {
|
|
6002
6080
|
const data = node?.data;
|
|
6003
6081
|
if (!data) {
|
|
@@ -6007,7 +6085,7 @@ class FsAiReportsComponent {
|
|
|
6007
6085
|
this._prompt.confirm({
|
|
6008
6086
|
title: 'Confirm',
|
|
6009
6087
|
template: folder
|
|
6010
|
-
?
|
|
6088
|
+
? this._deleteFolderMessage(data)
|
|
6011
6089
|
: 'Are you sure you would like to delete this report?',
|
|
6012
6090
|
})
|
|
6013
6091
|
.pipe(switchMap(() => (folder
|
|
@@ -6018,15 +6096,41 @@ class FsAiReportsComponent {
|
|
|
6018
6096
|
this._load();
|
|
6019
6097
|
});
|
|
6020
6098
|
}
|
|
6099
|
+
// Counted from the node already on screen, live rows only: in Show Deleted
|
|
6100
|
+
// mode the folder can also hold rows that are gone already, and those are not
|
|
6101
|
+
// leaving with it.
|
|
6102
|
+
_deleteFolderMessage(folder) {
|
|
6103
|
+
const count = (node) => node.children
|
|
6104
|
+
.filter((child) => !child.deleted)
|
|
6105
|
+
.reduce((total, child) => {
|
|
6106
|
+
const below = count(child);
|
|
6107
|
+
return {
|
|
6108
|
+
folders: total.folders + below.folders + (child.type === ReportNodeType.Folder ? 1 : 0),
|
|
6109
|
+
reports: total.reports + below.reports + (child.type === ReportNodeType.Report ? 1 : 0),
|
|
6110
|
+
};
|
|
6111
|
+
}, { folders: 0, reports: 0 });
|
|
6112
|
+
const { folders, reports } = count(folder);
|
|
6113
|
+
const contents = [
|
|
6114
|
+
folders ? `${folders} ${folders === 1 ? 'folder' : 'folders'}` : '',
|
|
6115
|
+
reports ? `${reports} ${reports === 1 ? 'report' : 'reports'}` : '',
|
|
6116
|
+
].filter(Boolean);
|
|
6117
|
+
const inside = contents.length ? ` and the ${contents.join(' and ')} inside it` : '';
|
|
6118
|
+
return `Are you sure you would like to delete the folder "${folder.name}"${inside}? It can be restored from Show Deleted.`;
|
|
6119
|
+
}
|
|
6120
|
+
// Both endpoints also restore any deleted folder above the row, so a row that
|
|
6121
|
+
// is live itself but hidden by a deleted folder is restored by the same call.
|
|
6021
6122
|
_restore(node) {
|
|
6022
6123
|
const data = node?.data;
|
|
6023
6124
|
if (!data) {
|
|
6024
6125
|
return;
|
|
6025
6126
|
}
|
|
6026
|
-
|
|
6127
|
+
const folder = data.type === ReportNodeType.Folder;
|
|
6128
|
+
(folder
|
|
6129
|
+
? this._reportData.undeleteFolder(data.id)
|
|
6130
|
+
: this._reportData.undelete(data.id))
|
|
6027
6131
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
6028
6132
|
.subscribe(() => {
|
|
6029
|
-
this._message.success('Restored report');
|
|
6133
|
+
this._message.success(folder ? 'Restored folder' : 'Restored report');
|
|
6030
6134
|
this._load();
|
|
6031
6135
|
});
|
|
6032
6136
|
}
|
|
@@ -6046,17 +6150,18 @@ class FsAiReportsComponent {
|
|
|
6046
6150
|
return segments;
|
|
6047
6151
|
}
|
|
6048
6152
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6049
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FsAiReportsComponent, isStandalone: true, selector: "fs-ai-reports", inputs: { basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, openPattern: { classPropertyName: "openPattern", publicName: "openPattern", isSignal: true, isRequired: false, transformFunction: null }, createPattern: { classPropertyName: "createPattern", publicName: "createPattern", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "_tree", first: true, predicate: FsTreeComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<fs-filter [config]=\"filterConfig\"></fs-filter>\n\n<ng-container *fsSkeleton=\"nodes()\">\n <fs-tree [config]=\"treeConfig\">\n <ng-template\n fsTreeNode\n let-data=\"data\">\n <div
|
|
6153
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FsAiReportsComponent, isStandalone: true, selector: "fs-ai-reports", inputs: { basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, openPattern: { classPropertyName: "openPattern", publicName: "openPattern", isSignal: true, isRequired: false, transformFunction: null }, createPattern: { classPropertyName: "createPattern", publicName: "createPattern", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "_tree", first: true, predicate: FsTreeComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<fs-filter [config]=\"filterConfig\"></fs-filter>\n\n<ng-container *fsSkeleton=\"nodes()\">\n <fs-tree [config]=\"treeConfig\">\n <ng-template\n fsTreeNode\n let-data=\"data\">\n <div\n class=\"fs-row.gap-sm.align-center node-row\"\n [class.deleted]=\"data.deleted\">\n <mat-icon class=\"node-icon\">\n {{ data.type === nodeType.Folder ? 'folder' : 'insert_chart_outlined' }}\n </mat-icon>\n\n @if (data.type === nodeType.Folder || data.deleted) {\n <!-- A deleted report can't be opened (the backend only assembles\n live ones, and one under a deleted folder is not on the live\n listing), so in Show Deleted mode the name is plain text \u2014\n restore it from the row menu first. -->\n <span class=\"node-name\">{{ data.name }}</span>\n } @else {\n <a\n class=\"node-name\"\n [routerLink]=\"openLink(data.report)\">\n {{ data.name }}\n </a>\n }\n\n <!-- Only on the row somebody deleted. Everything under a deleted\n folder is muted with it, but was not deleted itself. -->\n @if (data.stateDeleted) {\n <fs-chip size=\"tiny\">Deleted</fs-chip>\n }\n </div>\n </ng-template>\n </fs-tree>\n\n @if (!nodes().length) {\n <p class=\"small tree-empty\">\n No reports yet. Create one, or add a folder to group them.\n </p>\n }\n</ng-container>\n", styles: ["a{cursor:pointer}fs-tree .node>.container{flex:1;min-width:0}fs-tree .node>.container>.content{min-width:0}fs-tree .node>.container>button{margin-top:-5px}.node-row{min-width:0}.node-row.deleted{opacity:.55}.node-icon{font-size:18px;width:18px;height:18px;flex:none}.node-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-empty{margin-top:10px}\n"], dependencies: [{ kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i1$4.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i4.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }, { kind: "ngmodule", type: FsTreeModule }, { kind: "component", type: i4$1.FsTreeComponent, selector: "fs-tree", inputs: ["config"] }, { kind: "directive", type: i4$1.FsTreeNodeDirective, selector: "[fsTreeNode]", inputs: ["class"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6050
6154
|
}
|
|
6051
6155
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, decorators: [{
|
|
6052
6156
|
type: Component,
|
|
6053
6157
|
args: [{ selector: 'fs-ai-reports', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
6158
|
+
FsChipModule,
|
|
6054
6159
|
FsFilterModule,
|
|
6055
6160
|
FsSkeletonModule,
|
|
6056
6161
|
FsTreeModule,
|
|
6057
6162
|
MatIconModule,
|
|
6058
6163
|
RouterLink,
|
|
6059
|
-
], template: "<fs-filter [config]=\"filterConfig\"></fs-filter>\n\n<ng-container *fsSkeleton=\"nodes()\">\n <fs-tree [config]=\"treeConfig\">\n <ng-template\n fsTreeNode\n let-data=\"data\">\n <div
|
|
6164
|
+
], template: "<fs-filter [config]=\"filterConfig\"></fs-filter>\n\n<ng-container *fsSkeleton=\"nodes()\">\n <fs-tree [config]=\"treeConfig\">\n <ng-template\n fsTreeNode\n let-data=\"data\">\n <div\n class=\"fs-row.gap-sm.align-center node-row\"\n [class.deleted]=\"data.deleted\">\n <mat-icon class=\"node-icon\">\n {{ data.type === nodeType.Folder ? 'folder' : 'insert_chart_outlined' }}\n </mat-icon>\n\n @if (data.type === nodeType.Folder || data.deleted) {\n <!-- A deleted report can't be opened (the backend only assembles\n live ones, and one under a deleted folder is not on the live\n listing), so in Show Deleted mode the name is plain text \u2014\n restore it from the row menu first. -->\n <span class=\"node-name\">{{ data.name }}</span>\n } @else {\n <a\n class=\"node-name\"\n [routerLink]=\"openLink(data.report)\">\n {{ data.name }}\n </a>\n }\n\n <!-- Only on the row somebody deleted. Everything under a deleted\n folder is muted with it, but was not deleted itself. -->\n @if (data.stateDeleted) {\n <fs-chip size=\"tiny\">Deleted</fs-chip>\n }\n </div>\n </ng-template>\n </fs-tree>\n\n @if (!nodes().length) {\n <p class=\"small tree-empty\">\n No reports yet. Create one, or add a folder to group them.\n </p>\n }\n</ng-container>\n", styles: ["a{cursor:pointer}fs-tree .node>.container{flex:1;min-width:0}fs-tree .node>.container>.content{min-width:0}fs-tree .node>.container>button{margin-top:-5px}.node-row{min-width:0}.node-row.deleted{opacity:.55}.node-icon{font-size:18px;width:18px;height:18px;flex:none}.node-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-empty{margin-top:10px}\n"] }]
|
|
6060
6165
|
}], ctorParameters: () => [] });
|
|
6061
6166
|
|
|
6062
6167
|
// A single report: the AI chat on the left, the report canvas on the right,
|
|
@@ -6438,8 +6543,8 @@ class FsAiReportComponent {
|
|
|
6438
6543
|
ReportPdfService,
|
|
6439
6544
|
ReportExportService,
|
|
6440
6545
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
6441
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
6442
|
-
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }, { propertyName: "_canvas", first: true, predicate: ReportCanvasComponent, descendants: true }], ngImport: i0, template: "<div class=\"fs-ai-report fs-column\">\n\n <div class=\"fs-ai-report__header fs-row.align-center\">\n <h2 class=\"fs-ai-report__title fs-flex\">{{ report?.name }}</h2>\n\n @if (report) {\n <!-- Report-level filters, inline with the toolbar actions. fs-filter\n reads its config once at init, so it's keyed on the filter\n signature: when the report-level filter set changes the block is\n recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter\n class=\"fs-ai-report__filters\"\n [config]=\"reportFilterConfig\">\n </fs-filter>\n }\n }\n\n <!-- What layout editing can add or switch to, and the way out of it.\n One style for all three \u2014 outlined, icon and text. \"Header & footer\"\n is a toggle: it keeps its label and turns gray while it is on. -->\n @if (editMode) {\n <div class=\"fs-ai-report__edit-tools fs-row.align-center.gap-xs\">\n <button\n mat-stroked-button\n type=\"button\"\n matTooltip=\"Add a block of text\"\n (click)=\"addText()\">\n <mat-icon>title</mat-icon>\n Add text\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"fs-ai-report__master\"\n [class.active]=\"isMasterMode\"\n [matTooltip]=\"isMasterMode ? 'Back to editing this page' : 'Edit the header and footer shown on every page'\"\n (click)=\"toggleMasterMode()\">\n <mat-icon>web_asset</mat-icon>\n Header & footer\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"toggleEditMode()\">\n <mat-icon>check</mat-icon>\n Done editing\n </button>\n </div>\n }\n\n <!-- One icon for both states: it reads as pressed while editing rather\n than swapping to something else. -->\n <button\n mat-icon-button\n type=\"button\"\n class=\"fs-ai-report__toggle\"\n [class.active]=\"editMode\"\n [matTooltip]=\"editMode ? 'Done editing' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>dashboard_customize</mat-icon>\n </button>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Export\"\n [fsMenuTriggerFor]=\"exportMenu\">\n <mat-icon>download</mat-icon>\n </button>\n <fs-menu #exportMenu>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n Export PDF\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportData()\">\n Export Raw Data\n </ng-template>\n @if ((report?.pages?.length ?? 0) > 1) {\n <ng-template\n fs-menu-item\n (click)=\"exportData(true)\">\n Export Raw Data (This Page)\n </ng-template>\n }\n </fs-menu>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Report settings\"\n (click)=\"reportSettings()\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n\n <div\n #split\n class=\"fs-ai-report__split fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n [basePath]=\"basePath()\"\n [requestData]=\"chatRequestData()\"\n [ready]=\"chatPageId() !== null\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n\n <div class=\"viewer fs-flex fs-column\">\n @if (report) {\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n [pageId]=\"pageId\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (pageChanged)=\"onCanvasPageChanged($event)\">\n </app-report-canvas>\n }\n </div>\n </div>\n</div>\n", styles: [".fs-ai-report{height:100%;min-height:500px;display:flex;flex-direction:column}.fs-ai-report__header{flex:0 0 auto;margin-bottom:10px}.fs-ai-report__title{margin:0;min-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__filters{flex:0 1 auto;min-width:0;display:block;margin:0 5px 0 0}.fs-ai-report__filters ::ng-deep .filter-bar-container{align-items:center!important}.fs-ai-report__filters ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report__toggle.active{background:#e3ecf7;color:var(--brand-primary-color, #2196f3)}.fs-ai-report__master.active{background:#eceff3}.fs-ai-report__edit-tools{margin:0 5px}.fs-ai-report__split{flex:1 1 auto;min-height:0}.fs-ai-report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:400px;min-width:0;border:none}.fs-ai-report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.fs-ai-report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.fs-ai-report .resizer:before{content:\"\";width:3px;border-radius:3px;background:transparent;transition:background-color .12s ease}.fs-ai-report .resizer:hover:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fs-ai-report__split.resizing .resizer:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing .chat,.fs-ai-report__split.resizing .viewer{pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$1.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$1.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "directive", type: i2$1.FsMenuTriggerDirective, selector: "[fsMenuTriggerFor]", inputs: ["fsMenuTriggerFor"] }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "ready", "introMessage", "showThinking"], outputs: ["response", "showThinkingChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$
|
|
6546
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D4xJt3zs.mjs').then((module) => module.default) }),
|
|
6547
|
+
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }, { propertyName: "_canvas", first: true, predicate: ReportCanvasComponent, descendants: true }], ngImport: i0, template: "<div class=\"fs-ai-report fs-column\">\n\n <div class=\"fs-ai-report__header fs-row.align-center\">\n <h2 class=\"fs-ai-report__title fs-flex\">{{ report?.name }}</h2>\n\n @if (report) {\n <!-- Report-level filters, inline with the toolbar actions. fs-filter\n reads its config once at init, so it's keyed on the filter\n signature: when the report-level filter set changes the block is\n recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter\n class=\"fs-ai-report__filters\"\n [config]=\"reportFilterConfig\">\n </fs-filter>\n }\n }\n\n <!-- What layout editing can add or switch to, and the way out of it.\n One style for all three \u2014 outlined, icon and text. \"Header & footer\"\n is a toggle: it keeps its label and turns gray while it is on. -->\n @if (editMode) {\n <div class=\"fs-ai-report__edit-tools fs-row.align-center.gap-xs\">\n <button\n mat-stroked-button\n type=\"button\"\n matTooltip=\"Add a block of text\"\n (click)=\"addText()\">\n <mat-icon>title</mat-icon>\n Add text\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"fs-ai-report__master\"\n [class.active]=\"isMasterMode\"\n [matTooltip]=\"isMasterMode ? 'Back to editing this page' : 'Edit the header and footer shown on every page'\"\n (click)=\"toggleMasterMode()\">\n <mat-icon>web_asset</mat-icon>\n Header & footer\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"toggleEditMode()\">\n <mat-icon>check</mat-icon>\n Done editing\n </button>\n </div>\n }\n\n <!-- One icon for both states: it reads as pressed while editing rather\n than swapping to something else. -->\n <button\n mat-icon-button\n type=\"button\"\n class=\"fs-ai-report__toggle\"\n [class.active]=\"editMode\"\n [matTooltip]=\"editMode ? 'Done editing' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>dashboard_customize</mat-icon>\n </button>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Export\"\n [fsMenuTriggerFor]=\"exportMenu\">\n <mat-icon>download</mat-icon>\n </button>\n <fs-menu #exportMenu>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n Export PDF\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportData()\">\n Export Raw Data\n </ng-template>\n @if ((report?.pages?.length ?? 0) > 1) {\n <ng-template\n fs-menu-item\n (click)=\"exportData(true)\">\n Export Raw Data (This Page)\n </ng-template>\n }\n </fs-menu>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Report settings\"\n (click)=\"reportSettings()\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n\n <div\n #split\n class=\"fs-ai-report__split fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n [basePath]=\"basePath()\"\n [requestData]=\"chatRequestData()\"\n [ready]=\"chatPageId() !== null\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n\n <div class=\"viewer fs-flex fs-column\">\n @if (report) {\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n [pageId]=\"pageId\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (pageChanged)=\"onCanvasPageChanged($event)\">\n </app-report-canvas>\n }\n </div>\n </div>\n</div>\n", styles: [".fs-ai-report{height:100%;min-height:500px;display:flex;flex-direction:column}.fs-ai-report__header{flex:0 0 auto;margin-bottom:10px}.fs-ai-report__title{margin:0;min-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__filters{flex:0 1 auto;min-width:0;display:block;margin:0 5px 0 0}.fs-ai-report__filters ::ng-deep .filter-bar-container{align-items:center!important}.fs-ai-report__filters ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report__toggle.active{background:#e3ecf7;color:var(--brand-primary-color, #2196f3)}.fs-ai-report__master.active{background:#eceff3}.fs-ai-report__edit-tools{margin:0 5px}.fs-ai-report__split{flex:1 1 auto;min-height:0}.fs-ai-report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:400px;min-width:0;border:none}.fs-ai-report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.fs-ai-report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.fs-ai-report .resizer:before{content:\"\";width:3px;border-radius:3px;background:transparent;transition:background-color .12s ease}.fs-ai-report .resizer:hover:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fs-ai-report__split.resizing .resizer:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing .chat,.fs-ai-report__split.resizing .viewer{pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$1.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$1.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "directive", type: i2$1.FsMenuTriggerDirective, selector: "[fsMenuTriggerFor]", inputs: ["fsMenuTriggerFor"] }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "ready", "introMessage", "showThinking"], outputs: ["response", "showThinkingChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3$3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ReportCanvasComponent, selector: "app-report-canvas", inputs: ["report", "editMode", "pageId"], outputs: ["componentSettings", "reportChanged", "pageChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6443
6548
|
}
|
|
6444
6549
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, decorators: [{
|
|
6445
6550
|
type: Component,
|
|
@@ -6452,7 +6557,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6452
6557
|
ReportPdfService,
|
|
6453
6558
|
ReportExportService,
|
|
6454
6559
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
6455
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
6560
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-D4xJt3zs.mjs').then((module) => module.default) }),
|
|
6456
6561
|
], imports: [
|
|
6457
6562
|
FsFilterModule,
|
|
6458
6563
|
FsMenuModule,
|
|
@@ -6485,5 +6590,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6485
6590
|
* Generated bundle index. Do not edit.
|
|
6486
6591
|
*/
|
|
6487
6592
|
|
|
6488
|
-
export { FsAiReportsComponent as F, GRANULARITY_OPTIONS as G, REPORT_CHART_COLORS_CSS as R, ReportComponent as a, FsAiReportComponent as b, ReportData as c, ReportService as d, ReportFilterStateService as e, ReportNodeType as f, ReportState as g
|
|
6489
|
-
//# sourceMappingURL=firestitch-report-firestitch-report-
|
|
6593
|
+
export { FsAiReportsComponent as F, GRANULARITY_OPTIONS as G, REPORT_CHART_COLORS_CSS as R, ReportComponent as a, FsAiReportComponent as b, ReportData as c, ReportService as d, ReportFilterStateService as e, ReportNodeType as f, ReportState as g };
|
|
6594
|
+
//# sourceMappingURL=firestitch-report-firestitch-report-DY8HwvAU.mjs.map
|