@firestitch/report 18.7.0 → 18.8.1

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.
Files changed (64) hide show
  1. package/app/reports/components/component-kpi/component-kpi.component.d.ts +12 -3
  2. package/app/reports/components/component-kpi/components/sparkline/sparkline.component.d.ts +8 -0
  3. package/app/reports/components/component-list/component-list.component.d.ts +7 -1
  4. package/app/reports/components/component-note/component-note.component.d.ts +12 -0
  5. package/app/reports/components/report-canvas/report-canvas.component.d.ts +0 -9
  6. package/app/reports/components/report-component/report-component.component.d.ts +2 -11
  7. package/app/reports/data/report.data.d.ts +0 -2
  8. package/app/reports/dialogs/component-settings/component-settings.component.d.ts +34 -2
  9. package/app/reports/dialogs/report-settings/report-settings.component.d.ts +0 -1
  10. package/app/reports/enums/report-change-polarity.enum.d.ts +5 -0
  11. package/app/reports/enums/report-kpi-trend.enum.d.ts +5 -0
  12. package/app/reports/export/report-export-collector.service.d.ts +29 -1
  13. package/app/reports/export/report-pdf.service.d.ts +5 -0
  14. package/app/reports/export/report-pptx.service.d.ts +7 -0
  15. package/app/reports/format.d.ts +29 -1
  16. package/app/reports/interfaces/report-chat-viewer-filter.interface.d.ts +2 -0
  17. package/app/reports/interfaces/report.interface.d.ts +124 -4
  18. package/app/reports/kpi-card.d.ts +48 -0
  19. package/app/reports/layout.d.ts +0 -9
  20. package/app/reports/list-config.d.ts +8 -1
  21. package/app/reports/option-builder.d.ts +7 -0
  22. package/app/reports/output-notes.d.ts +14 -0
  23. package/app/reports/pipes/report-output.pipe.d.ts +10 -0
  24. package/app/reports/services/report-filter-state.service.d.ts +1 -0
  25. package/app/reports/theme/palette.d.ts +5 -0
  26. package/app/reports/theme/typography.d.ts +1 -0
  27. package/esm2022/app/reports/components/component-chart/component-chart.component.mjs +8 -4
  28. package/esm2022/app/reports/components/component-kpi/component-kpi.component.mjs +50 -17
  29. package/esm2022/app/reports/components/component-kpi/components/sparkline/sparkline.component.mjs +19 -0
  30. package/esm2022/app/reports/components/component-list/component-list.component.mjs +66 -6
  31. package/esm2022/app/reports/components/component-note/component-note.component.mjs +30 -0
  32. package/esm2022/app/reports/components/report-canvas/report-canvas.component.mjs +7 -56
  33. package/esm2022/app/reports/components/report-component/report-component.component.mjs +21 -82
  34. package/esm2022/app/reports/data/report.data.mjs +5 -5
  35. package/esm2022/app/reports/dialogs/component-settings/component-settings.component.mjs +133 -13
  36. package/esm2022/app/reports/dialogs/report-settings/report-settings.component.mjs +4 -8
  37. package/esm2022/app/reports/enums/report-change-polarity.enum.mjs +11 -0
  38. package/esm2022/app/reports/enums/report-kpi-trend.enum.mjs +10 -0
  39. package/esm2022/app/reports/export/report-export-collector.service.mjs +123 -10
  40. package/esm2022/app/reports/export/report-pdf.service.mjs +257 -44
  41. package/esm2022/app/reports/export/report-pptx.service.mjs +324 -51
  42. package/esm2022/app/reports/format.mjs +452 -3
  43. package/esm2022/app/reports/interfaces/report-chat-viewer-filter.interface.mjs +1 -1
  44. package/esm2022/app/reports/interfaces/report.interface.mjs +1 -1
  45. package/esm2022/app/reports/kpi-card.mjs +187 -0
  46. package/esm2022/app/reports/layout.mjs +1 -46
  47. package/esm2022/app/reports/list-config.mjs +33 -1
  48. package/esm2022/app/reports/option-builder.mjs +637 -54
  49. package/esm2022/app/reports/output-notes.mjs +204 -0
  50. package/esm2022/app/reports/pipes/report-output.pipe.mjs +28 -0
  51. package/esm2022/app/reports/services/report-filter-state.service.mjs +40 -3
  52. package/esm2022/app/reports/theme/palette.mjs +22 -1
  53. package/esm2022/app/reports/theme/typography.mjs +5 -1
  54. package/esm2022/app/reports/views/report/fs-ai-report.component.mjs +2 -2
  55. package/esm2022/app/reports/views/report/report.component.mjs +3 -4
  56. package/esm2022/public_api.mjs +2 -1
  57. package/fesm2022/{firestitch-report-echarts-u5ImuZT5.mjs → firestitch-report-echarts-CFnbJx44.mjs} +2 -2
  58. package/fesm2022/{firestitch-report-echarts-u5ImuZT5.mjs.map → firestitch-report-echarts-CFnbJx44.mjs.map} +1 -1
  59. package/fesm2022/{firestitch-report-firestitch-report-Cz-kA_id.mjs → firestitch-report-firestitch-report-CSylkyko.mjs} +2735 -507
  60. package/fesm2022/firestitch-report-firestitch-report-CSylkyko.mjs.map +1 -0
  61. package/fesm2022/firestitch-report.mjs +1 -1
  62. package/package.json +1 -1
  63. package/public_api.d.ts +1 -0
  64. package/fesm2022/firestitch-report-firestitch-report-Cz-kA_id.mjs.map +0 -1
@@ -0,0 +1,48 @@
1
+ import { ReportChangePolarity } from './enums/report-change-polarity.enum';
2
+ import { ReportKpiTrend } from './enums/report-kpi-trend.enum';
3
+ import { ChartGranularity, ComponentData, KpiCardSettings, KpiConfig, ReportFormatContext } from './interfaces/report.interface';
4
+ export declare const KPI_SPARKLINE_DEFAULT_GRANULARITY: ChartGranularity;
5
+ export declare const KPI_SPARKLINE_DEFAULT_PERIODS = 12;
6
+ export interface KpiCardContext {
7
+ format: ReportFormatContext;
8
+ timezone: string;
9
+ }
10
+ export interface KpiCardDelta {
11
+ text: string;
12
+ trend: ReportKpiTrend;
13
+ tone: ReportChangePolarity;
14
+ compared: string;
15
+ }
16
+ export interface KpiCardTarget {
17
+ text: string;
18
+ progress: string;
19
+ fill: number | null;
20
+ tone: ReportChangePolarity;
21
+ }
22
+ export interface KpiCardSparkline {
23
+ values: (number | null)[];
24
+ partial: boolean;
25
+ }
26
+ export interface KpiCardView {
27
+ value: string;
28
+ label: string;
29
+ delta: KpiCardDelta | null;
30
+ window: string;
31
+ target: KpiCardTarget | null;
32
+ sparkline: KpiCardSparkline | null;
33
+ }
34
+ export interface KpiSparklinePoint {
35
+ x: number;
36
+ y: number;
37
+ }
38
+ export interface KpiSparklineGeometry {
39
+ lines: string[];
40
+ dots: KpiSparklinePoint[];
41
+ last: (KpiSparklinePoint & {
42
+ hollow: boolean;
43
+ }) | null;
44
+ }
45
+ export declare function kpiCardView(config: KpiConfig | null | undefined, data: ComponentData | null | undefined, context: KpiCardContext): KpiCardView;
46
+ export declare function kpiSparklineGeometry(values: (number | null)[], partial: boolean): KpiSparklineGeometry;
47
+ export declare function kpiCardSettings(config: KpiConfig | null | undefined): KpiCardSettings;
48
+ export declare function sameKpiCardSettings(a: KpiCardSettings, b: KpiCardSettings): boolean;
@@ -1,14 +1,5 @@
1
- import { Report, ReportPage } from './interfaces/report.interface';
2
- export declare const FLOW_PADDING = 0.25;
3
1
  export declare const DEFAULT_COMPONENT_PADDING = 0.125;
4
2
  export declare const DEFAULT_HEADING_SIZE = 16;
5
- export interface ComponentGeometry {
6
- x: number;
7
- y: number;
8
- w: number;
9
- h: number;
10
- }
11
- export declare function pageGeometry(report: Report, page: ReportPage): Map<number, ComponentGeometry>;
12
3
  export declare const PAGE_DIMENSION_MIN = 1;
13
4
  export declare const PAGE_DIMENSION_MAX = 50;
14
5
  export declare const HEADER_FOOTER_MAX = 3;
@@ -1,4 +1,4 @@
1
- import { ListColumn, ListConfig, ReportSort } from './interfaces/report.interface';
1
+ import { ComponentData, ListColumn, ListConfig, NumberFormatSpec, OutputMeta, ReportSort } from './interfaces/report.interface';
2
2
  export declare const MAX_PINNED_COLUMNS = 3;
3
3
  export declare function listSortTerms(config: ListConfig | null | undefined): ReportSort[];
4
4
  export declare function listHideableColumns(config: ListConfig | null | undefined): string[];
@@ -7,3 +7,10 @@ export declare function listPinnedColumns(config: ListConfig | null | undefined)
7
7
  export declare function columnClasses(column: ListColumn, index: number, pinned: number): string;
8
8
  export declare const LIST_TOTAL_LABEL = "Total";
9
9
  export declare function listTotalsLabelColumn(columns: readonly ListColumn[], totals: Record<string, unknown> | null | undefined): string | null;
10
+ export interface ListOutputColumn {
11
+ output: OutputMeta;
12
+ measureSpec: NumberFormatSpec | null;
13
+ priorAlias: string | null;
14
+ floor: string | null;
15
+ }
16
+ export declare function listOutputColumns(columns: readonly ListColumn[], data: Pick<ComponentData, 'outputs' | 'cellMeasures' | 'comparison'> | null | undefined, authored: readonly ListColumn[]): Record<string, ListOutputColumn>;
@@ -6,6 +6,7 @@ import { ReportChartMarkStat } from './enums/report-chart-mark-stat.enum';
6
6
  import { AxisChartType, ChartConfig, ChartGranularity, ChartMeasure, ChartValueAxis, ComponentData, LegendPosition, ListColumn, ListConfig, ReportComponent, ReportFormatContext, ReportLocaleTag, WaterfallStepKind } from './interfaces/report.interface';
7
7
  export declare const PERCENT_AXIS_MAX = 1;
8
8
  export declare const PERCENT_AXIS_INTERVAL = 0.25;
9
+ export declare const RANK_AXIS_MIN = 1;
9
10
  export declare const STAT_LABEL: Record<ReportChartMarkStat, string>;
10
11
  export type ValueAxisIndex = 0 | 1;
11
12
  export interface DrawnReference {
@@ -18,6 +19,7 @@ export interface DrawnReference {
18
19
  interface DataLabelParams {
19
20
  value?: unknown;
20
21
  data?: unknown;
22
+ dataIndex?: number;
21
23
  }
22
24
  export type DataLabelFormatter = (params: DataLabelParams) => string;
23
25
  export type DataLabelPosition = 'top' | 'inside' | 'outside';
@@ -73,6 +75,11 @@ export interface ExtractedWaterfall {
73
75
  export declare const WATERFALL_TOTAL_LABEL = "Total";
74
76
  export declare function extractWaterfall(component: ReportComponent, data: ComponentData): ExtractedWaterfall;
75
77
  export declare function buildChartOption(component: ReportComponent, data: ComponentData, context: ReportFormatContext): EChartsOption;
78
+ export declare function priorSeriesStyles(config: ChartConfig, data: ComponentData, extracted: ExtractedChartSeries): {
79
+ colors: string[];
80
+ priors: boolean[];
81
+ } | null;
82
+ export declare function rankValueAxes(extracted: ExtractedChartSeries, data: ComponentData, config: ChartConfig): boolean[];
76
83
  export declare function referencePeak(references: DrawnReference[], axisIndex: ValueAxisIndex): number;
77
84
  export declare function resolveLegendPosition(config: ChartConfig, seriesCount: number, colorBy?: boolean): LegendPosition;
78
85
  export declare function dataLabelOption(mode: ReportChartDataLabels | undefined, formatter: DataLabelFormatter): SeriesDataLabels | null;
@@ -0,0 +1,14 @@
1
+ import { ComponentData, ReportLocaleTag } from './interfaces/report.interface';
2
+ export type ComponentNoteData = Pick<ComponentData, 'comparison' | 'timeframe' | 'outputs' | 'cellMeasures' | 'windows' | 'granularity' | 'kpi'>;
3
+ export interface ComponentNoteContext {
4
+ locale: ReportLocaleTag;
5
+ timezone: string;
6
+ }
7
+ export interface ComponentNoteParts {
8
+ windows?: boolean;
9
+ windowDescriptions?: boolean;
10
+ windowNotes?: boolean;
11
+ }
12
+ export declare function componentNoteLines(data: ComponentNoteData | null | undefined, context: ComponentNoteContext, parts?: ComponentNoteParts): string[];
13
+ export declare function outputColumnNote(column: string, data: ComponentNoteData | null | undefined, locale: ReportLocaleTag): string;
14
+ export declare function componentWindowsLine(data: ComponentNoteData, context: ComponentNoteContext): string;
@@ -0,0 +1,10 @@
1
+ import { PipeTransform } from '@angular/core';
2
+ import { FormattedOutputValue } from '../format';
3
+ import { ListColumn, ReportFormatContext } from '../interfaces/report.interface';
4
+ import { ListOutputColumn } from '../list-config';
5
+ import * as i0 from "@angular/core";
6
+ export declare class ReportOutputPipe implements PipeTransform {
7
+ transform(row: Record<string, unknown> | null | undefined, column: ListColumn, output: ListOutputColumn, context: ReportFormatContext): FormattedOutputValue;
8
+ static ɵfac: i0.ɵɵFactoryDeclaration<ReportOutputPipe, never>;
9
+ static ɵpipe: i0.ɵɵPipeDeclaration<ReportOutputPipe, "fsReportOutput", true>;
10
+ }
@@ -57,6 +57,7 @@ export declare class ReportFilterStateService {
57
57
  viewerFilters(): ReportChatViewerFilter[];
58
58
  resolveForReport(report: Report, pageId?: number | null): ResolvedFilterValue[];
59
59
  private _resolvedFields;
60
+ private _livePreset;
60
61
  private _seed;
61
62
  private _restore;
62
63
  private _restoreGroups;
@@ -1,3 +1,4 @@
1
+ import { ReportChangePolarity } from '../enums/report-change-polarity.enum';
1
2
  import { ReportChartColor } from '../enums/report-chart-color.enum';
2
3
  import { WaterfallStepKind } from '../interfaces/report.interface';
3
4
  export declare const REPORT_CHART_COLORS: string[];
@@ -5,3 +6,7 @@ export declare const REPORT_CHART_COLORS_CSS: string[];
5
6
  export declare const REPORT_CHART_COLOR_BY_NAME: Record<ReportChartColor, string>;
6
7
  export declare const REPORT_REFERENCE_COLOR = "52606d";
7
8
  export declare const REPORT_WATERFALL_COLORS: Record<WaterfallStepKind, string>;
9
+ export declare const REPORT_MUTED_TEXT_COLOR = "7b8794";
10
+ export declare const REPORT_KPI_CHANGE_COLORS: Record<ReportChangePolarity, string>;
11
+ export declare const REPORT_KPI_TRACK_COLOR = "e4e7eb";
12
+ export declare const REPORT_KPI_SPARKLINE_COLOR: string;
@@ -1,3 +1,4 @@
1
1
  export declare const REPORT_CHART_FONT_FAMILY = "Roboto, \"Helvetica Neue\", sans-serif";
2
2
  export declare const REPORT_AXIS_LABEL_FONT_SIZE = 11;
3
3
  export declare const REPORT_DATA_LABEL_FONT_SIZE = 10;
4
+ export declare const REPORT_KPI_VALUE_FONT_SIZE = 34;
@@ -2,12 +2,15 @@ import { ChangeDetectionStrategy, Component, computed, inject, input } from '@an
2
2
  import { NgxEchartsDirective } from 'ngx-echarts';
3
3
  import { buildChartOption } from '../../option-builder';
4
4
  import { ReportFilterStateService } from '../../services/report-filter-state.service';
5
+ import { ComponentNoteComponent } from '../component-note/component-note.component';
5
6
  import * as i0 from "@angular/core";
6
7
  // ECharts host for a chart component. Pure rendering: the parent owns the
7
8
  // data fetch; this maps (component config, rows, the open report's format
8
9
  // context) → option and exposes the chart instance for resize + the future
9
10
  // image exports (getDataURL needs the canvas renderer registered in
10
- // theme/echarts.ts).
11
+ // theme/echarts.ts). The shared note under the plot names the days a
12
+ // comparison compares and why windowed series read as they do; the series'
13
+ // tooltips describe the windows themselves.
11
14
  export class ComponentChartComponent {
12
15
  component = input.required();
13
16
  data = input(null);
@@ -35,12 +38,13 @@ export class ComponentChartComponent {
35
38
  }) ?? null;
36
39
  }
37
40
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
38
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ComponentChartComponent, isStandalone: true, selector: "app-report-component-chart", inputs: { component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\n class=\"chart-host\"\n echarts\n [options]=\"options() ?? {}\"\n [theme]=\"'report'\"\n [autoResize]=\"true\"\n (chartInit)=\"onChartInit($event)\">\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.chart-host{width:100%;height:100%}\n"], dependencies: [{ kind: "directive", type: NgxEchartsDirective, selector: "echarts, [echarts]", inputs: ["options", "theme", "initOpts", "merge", "autoResize", "loading", "loadingType", "loadingOpts"], outputs: ["chartInit", "optionsError", "chartClick", "chartDblClick", "chartMouseDown", "chartMouseMove", "chartMouseUp", "chartMouseOver", "chartMouseOut", "chartGlobalOut", "chartContextMenu", "chartHighlight", "chartDownplay", "chartSelectChanged", "chartLegendSelectChanged", "chartLegendSelected", "chartLegendUnselected", "chartLegendLegendSelectAll", "chartLegendLegendInverseSelect", "chartLegendScroll", "chartDataZoom", "chartDataRangeSelected", "chartGraphRoam", "chartGeoRoam", "chartTreeRoam", "chartTimelineChanged", "chartTimelinePlayChanged", "chartRestore", "chartDataViewChanged", "chartMagicTypeChanged", "chartGeoSelectChanged", "chartGeoSelected", "chartGeoUnselected", "chartAxisAreaSelected", "chartBrush", "chartBrushEnd", "chartBrushSelected", "chartGlobalCursorTaken", "chartRendered", "chartFinished"], exportAs: ["echarts"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
41
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ComponentChartComponent, isStandalone: true, selector: "app-report-component-chart", inputs: { component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\n class=\"chart-host\"\n echarts\n [options]=\"options() ?? {}\"\n [theme]=\"'report'\"\n [autoResize]=\"true\"\n (chartInit)=\"onChartInit($event)\">\n</div>\n<app-report-component-note [data]=\"data()\"></app-report-component-note>\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}.chart-host{flex:1 1 0;width:100%;min-height:0}\n"], dependencies: [{ kind: "component", type: ComponentNoteComponent, selector: "app-report-component-note", inputs: ["data", "windows", "windowDescriptions", "windowNotes"] }, { kind: "directive", type: NgxEchartsDirective, selector: "echarts, [echarts]", inputs: ["options", "theme", "initOpts", "merge", "autoResize", "loading", "loadingType", "loadingOpts"], outputs: ["chartInit", "optionsError", "chartClick", "chartDblClick", "chartMouseDown", "chartMouseMove", "chartMouseUp", "chartMouseOver", "chartMouseOut", "chartGlobalOut", "chartContextMenu", "chartHighlight", "chartDownplay", "chartSelectChanged", "chartLegendSelectChanged", "chartLegendSelected", "chartLegendUnselected", "chartLegendLegendSelectAll", "chartLegendLegendInverseSelect", "chartLegendScroll", "chartDataZoom", "chartDataRangeSelected", "chartGraphRoam", "chartGeoRoam", "chartTreeRoam", "chartTimelineChanged", "chartTimelinePlayChanged", "chartRestore", "chartDataViewChanged", "chartMagicTypeChanged", "chartGeoSelectChanged", "chartGeoSelected", "chartGeoUnselected", "chartAxisAreaSelected", "chartBrush", "chartBrushEnd", "chartBrushSelected", "chartGlobalCursorTaken", "chartRendered", "chartFinished"], exportAs: ["echarts"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
39
42
  }
40
43
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentChartComponent, decorators: [{
41
44
  type: Component,
42
45
  args: [{ selector: 'app-report-component-chart', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
46
+ ComponentNoteComponent,
43
47
  NgxEchartsDirective,
44
- ], template: "<div\n class=\"chart-host\"\n echarts\n [options]=\"options() ?? {}\"\n [theme]=\"'report'\"\n [autoResize]=\"true\"\n (chartInit)=\"onChartInit($event)\">\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.chart-host{width:100%;height:100%}\n"] }]
48
+ ], template: "<div\n class=\"chart-host\"\n echarts\n [options]=\"options() ?? {}\"\n [theme]=\"'report'\"\n [autoResize]=\"true\"\n (chartInit)=\"onChartInit($event)\">\n</div>\n<app-report-component-note [data]=\"data()\"></app-report-component-note>\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}.chart-host{flex:1 1 0;width:100%;min-height:0}\n"] }]
45
49
  }] });
46
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcG9uZW50LWNoYXJ0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3NyYy9hcHAvcmVwb3J0cy9jb21wb25lbnRzL2NvbXBvbmVudC1jaGFydC9jb21wb25lbnQtY2hhcnQuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9yZXBvcnRzL2NvbXBvbmVudHMvY29tcG9uZW50LWNoYXJ0L2NvbXBvbmVudC1jaGFydC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRzVGLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUdsRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN4RCxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQzs7QUFHdEYsMEVBQTBFO0FBQzFFLDBFQUEwRTtBQUMxRSwyRUFBMkU7QUFDM0Usb0VBQW9FO0FBQ3BFLHFCQUFxQjtBQVdyQixNQUFNLE9BQU8sdUJBQXVCO0lBRWxCLFNBQVMsR0FBRyxLQUFLLENBQUMsUUFBUSxFQUFtQixDQUFDO0lBQzlDLElBQUksR0FBRyxLQUFLLENBQXVCLElBQUksQ0FBQyxDQUFDO0lBRXpDLE9BQU8sR0FBRyxRQUFRLENBQXVCLEdBQUcsRUFBRTtRQUM1RCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7UUFFekIsT0FBTyxJQUFJO1lBQ1QsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxhQUFhLEVBQUUsQ0FBQztZQUM3RSxDQUFDLENBQUMsSUFBSSxDQUFDO0lBQ1gsQ0FBQyxDQUFDLENBQUM7SUFFSyxNQUFNLEdBQW1CLElBQUksQ0FBQztJQUNyQixZQUFZLEdBQUcsTUFBTSxDQUFDLHdCQUF3QixDQUFDLENBQUM7SUFFMUQsV0FBVyxDQUFDLEtBQWM7UUFDL0IsSUFBSSxDQUFDLE1BQU0sR0FBRyxLQUFLLENBQUM7SUFDdEIsQ0FBQztJQUVELDZEQUE2RDtJQUN0RCxNQUFNO1FBQ1gsSUFBSSxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUN4QixDQUFDO0lBRUQsd0RBQXdEO0lBQ2pELFVBQVUsQ0FBQyxVQUFVLEdBQUcsQ0FBQztRQUM5QixPQUFPLElBQUksQ0FBQyxNQUFNLEVBQUUsVUFBVSxDQUFDO1lBQzdCLElBQUksRUFBRSxLQUFLO1lBQ1gsVUFBVTtZQUNWLGVBQWUsRUFBRSxTQUFTO1NBQzNCLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDYixDQUFDO3dHQWhDVSx1QkFBdUI7NEZBQXZCLHVCQUF1QiwwVkN6QnBDLDZLQVFBLDBJRGNJLG1CQUFtQjs7NEZBR1YsdUJBQXVCO2tCQVZuQyxTQUFTOytCQUNFLDRCQUE0QixtQkFHckIsdUJBQXVCLENBQUMsTUFBTSxjQUNuQyxJQUFJLFdBQ1A7d0JBQ1AsbUJBQW1CO3FCQUNwQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENoYW5nZURldGVjdGlvblN0cmF0ZWd5LCBDb21wb25lbnQsIGNvbXB1dGVkLCBpbmplY3QsIGlucHV0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5cbmltcG9ydCB0eXBlIHsgRUNoYXJ0c09wdGlvbiwgRUNoYXJ0cyB9IGZyb20gJ2VjaGFydHMnO1xuaW1wb3J0IHsgTmd4RWNoYXJ0c0RpcmVjdGl2ZSB9IGZyb20gJ25neC1lY2hhcnRzJztcblxuaW1wb3J0IHsgQ29tcG9uZW50RGF0YSwgUmVwb3J0Q29tcG9uZW50IH0gZnJvbSAnLi4vLi4vaW50ZXJmYWNlcy9yZXBvcnQuaW50ZXJmYWNlJztcbmltcG9ydCB7IGJ1aWxkQ2hhcnRPcHRpb24gfSBmcm9tICcuLi8uLi9vcHRpb24tYnVpbGRlcic7XG5pbXBvcnQgeyBSZXBvcnRGaWx0ZXJTdGF0ZVNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9yZXBvcnQtZmlsdGVyLXN0YXRlLnNlcnZpY2UnO1xuXG5cbi8vIEVDaGFydHMgaG9zdCBmb3IgYSBjaGFydCBjb21wb25lbnQuIFB1cmUgcmVuZGVyaW5nOiB0aGUgcGFyZW50IG93bnMgdGhlXG4vLyBkYXRhIGZldGNoOyB0aGlzIG1hcHMgKGNvbXBvbmVudCBjb25maWcsIHJvd3MsIHRoZSBvcGVuIHJlcG9ydCdzIGZvcm1hdFxuLy8gY29udGV4dCkg4oaSIG9wdGlvbiBhbmQgZXhwb3NlcyB0aGUgY2hhcnQgaW5zdGFuY2UgZm9yIHJlc2l6ZSArIHRoZSBmdXR1cmVcbi8vIGltYWdlIGV4cG9ydHMgKGdldERhdGFVUkwgbmVlZHMgdGhlIGNhbnZhcyByZW5kZXJlciByZWdpc3RlcmVkIGluXG4vLyB0aGVtZS9lY2hhcnRzLnRzKS5cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ2FwcC1yZXBvcnQtY29tcG9uZW50LWNoYXJ0JyxcbiAgdGVtcGxhdGVVcmw6ICcuL2NvbXBvbmVudC1jaGFydC5jb21wb25lbnQuaHRtbCcsXG4gIHN0eWxlVXJsczogWycuL2NvbXBvbmVudC1jaGFydC5jb21wb25lbnQuc2NzcyddLFxuICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1xuICAgIE5neEVjaGFydHNEaXJlY3RpdmUsXG4gIF0sXG59KVxuZXhwb3J0IGNsYXNzIENvbXBvbmVudENoYXJ0Q29tcG9uZW50IHtcblxuICBwdWJsaWMgcmVhZG9ubHkgY29tcG9uZW50ID0gaW5wdXQucmVxdWlyZWQ8UmVwb3J0Q29tcG9uZW50PigpO1xuICBwdWJsaWMgcmVhZG9ubHkgZGF0YSA9IGlucHV0PENvbXBvbmVudERhdGEgfCBudWxsPihudWxsKTtcblxuICBwdWJsaWMgcmVhZG9ubHkgb3B0aW9ucyA9IGNvbXB1dGVkPEVDaGFydHNPcHRpb24gfCBudWxsPigoKSA9PiB7XG4gICAgY29uc3QgZGF0YSA9IHRoaXMuZGF0YSgpO1xuXG4gICAgcmV0dXJuIGRhdGFcbiAgICAgID8gYnVpbGRDaGFydE9wdGlvbih0aGlzLmNvbXBvbmVudCgpLCBkYXRhLCB0aGlzLl9maWx0ZXJTdGF0ZS5mb3JtYXRDb250ZXh0KCkpXG4gICAgICA6IG51bGw7XG4gIH0pO1xuXG4gIHByaXZhdGUgX2NoYXJ0OiBFQ2hhcnRzIHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgcmVhZG9ubHkgX2ZpbHRlclN0YXRlID0gaW5qZWN0KFJlcG9ydEZpbHRlclN0YXRlU2VydmljZSk7XG5cbiAgcHVibGljIG9uQ2hhcnRJbml0KGNoYXJ0OiBFQ2hhcnRzKTogdm9pZCB7XG4gICAgdGhpcy5fY2hhcnQgPSBjaGFydDtcbiAgfVxuXG4gIC8vIENhbGxlZCBieSB0aGUgY2FudmFzIHdoZW4gdGhlIGJveCBpcyByZXNpemVkIGluIGVkaXQgbW9kZS5cbiAgcHVibGljIHJlc2l6ZSgpOiB2b2lkIHtcbiAgICB0aGlzLl9jaGFydD8ucmVzaXplKCk7XG4gIH1cblxuICAvLyBIaWdoLXJlcyBpbWFnZSBmb3IgdGhlIChsYXRlci1waGFzZSkgUFBUL1BERiBleHBvcnRzLlxuICBwdWJsaWMgZ2V0RGF0YVVSTChwaXhlbFJhdGlvID0gMyk6IHN0cmluZyB8IG51bGwge1xuICAgIHJldHVybiB0aGlzLl9jaGFydD8uZ2V0RGF0YVVSTCh7XG4gICAgICB0eXBlOiAncG5nJyxcbiAgICAgIHBpeGVsUmF0aW8sXG4gICAgICBiYWNrZ3JvdW5kQ29sb3I6ICcjZmZmZmZmJyxcbiAgICB9KSA/PyBudWxsO1xuICB9XG59XG4iLCI8ZGl2XG4gIGNsYXNzPVwiY2hhcnQtaG9zdFwiXG4gIGVjaGFydHNcbiAgW29wdGlvbnNdPVwib3B0aW9ucygpID8/IHt9XCJcbiAgW3RoZW1lXT1cIidyZXBvcnQnXCJcbiAgW2F1dG9SZXNpemVdPVwidHJ1ZVwiXG4gIChjaGFydEluaXQpPVwib25DaGFydEluaXQoJGV2ZW50KVwiPlxuPC9kaXY+XG4iXX0=
50
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcG9uZW50LWNoYXJ0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3NyYy9hcHAvcmVwb3J0cy9jb21wb25lbnRzL2NvbXBvbmVudC1jaGFydC9jb21wb25lbnQtY2hhcnQuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9yZXBvcnRzL2NvbXBvbmVudHMvY29tcG9uZW50LWNoYXJ0L2NvbXBvbmVudC1jaGFydC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRzVGLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUdsRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN4RCxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQztBQUN0RixPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQzs7QUFHcEYsMEVBQTBFO0FBQzFFLDBFQUEwRTtBQUMxRSwyRUFBMkU7QUFDM0Usb0VBQW9FO0FBQ3BFLHFFQUFxRTtBQUNyRSwyRUFBMkU7QUFDM0UsNENBQTRDO0FBWTVDLE1BQU0sT0FBTyx1QkFBdUI7SUFFbEIsU0FBUyxHQUFHLEtBQUssQ0FBQyxRQUFRLEVBQW1CLENBQUM7SUFDOUMsSUFBSSxHQUFHLEtBQUssQ0FBdUIsSUFBSSxDQUFDLENBQUM7SUFFekMsT0FBTyxHQUFHLFFBQVEsQ0FBdUIsR0FBRyxFQUFFO1FBQzVELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUV6QixPQUFPLElBQUk7WUFDVCxDQUFDLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsWUFBWSxDQUFDLGFBQWEsRUFBRSxDQUFDO1lBQzdFLENBQUMsQ0FBQyxJQUFJLENBQUM7SUFDWCxDQUFDLENBQUMsQ0FBQztJQUVLLE1BQU0sR0FBbUIsSUFBSSxDQUFDO0lBQ3JCLFlBQVksR0FBRyxNQUFNLENBQUMsd0JBQXdCLENBQUMsQ0FBQztJQUUxRCxXQUFXLENBQUMsS0FBYztRQUMvQixJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQztJQUN0QixDQUFDO0lBRUQsNkRBQTZEO0lBQ3RELE1BQU07UUFDWCxJQUFJLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBQ3hCLENBQUM7SUFFRCx3REFBd0Q7SUFDakQsVUFBVSxDQUFDLFVBQVUsR0FBRyxDQUFDO1FBQzlCLE9BQU8sSUFBSSxDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUM7WUFDN0IsSUFBSSxFQUFFLEtBQUs7WUFDWCxVQUFVO1lBQ1YsZUFBZSxFQUFFLFNBQVM7U0FDM0IsQ0FBQyxJQUFJLElBQUksQ0FBQztJQUNiLENBQUM7d0dBaENVLHVCQUF1Qjs0RkFBdkIsdUJBQXVCLDBWQzdCcEMsd1BBU0EsMktEZ0JJLHNCQUFzQix3SUFDdEIsbUJBQW1COzs0RkFHVix1QkFBdUI7a0JBWG5DLFNBQVM7K0JBQ0UsNEJBQTRCLG1CQUdyQix1QkFBdUIsQ0FBQyxNQUFNLGNBQ25DLElBQUksV0FDUDt3QkFDUCxzQkFBc0I7d0JBQ3RCLG1CQUFtQjtxQkFDcEIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneSwgQ29tcG9uZW50LCBjb21wdXRlZCwgaW5qZWN0LCBpbnB1dCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG5pbXBvcnQgdHlwZSB7IEVDaGFydHNPcHRpb24sIEVDaGFydHMgfSBmcm9tICdlY2hhcnRzJztcbmltcG9ydCB7IE5neEVjaGFydHNEaXJlY3RpdmUgfSBmcm9tICduZ3gtZWNoYXJ0cyc7XG5cbmltcG9ydCB7IENvbXBvbmVudERhdGEsIFJlcG9ydENvbXBvbmVudCB9IGZyb20gJy4uLy4uL2ludGVyZmFjZXMvcmVwb3J0LmludGVyZmFjZSc7XG5pbXBvcnQgeyBidWlsZENoYXJ0T3B0aW9uIH0gZnJvbSAnLi4vLi4vb3B0aW9uLWJ1aWxkZXInO1xuaW1wb3J0IHsgUmVwb3J0RmlsdGVyU3RhdGVTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vc2VydmljZXMvcmVwb3J0LWZpbHRlci1zdGF0ZS5zZXJ2aWNlJztcbmltcG9ydCB7IENvbXBvbmVudE5vdGVDb21wb25lbnQgfSBmcm9tICcuLi9jb21wb25lbnQtbm90ZS9jb21wb25lbnQtbm90ZS5jb21wb25lbnQnO1xuXG5cbi8vIEVDaGFydHMgaG9zdCBmb3IgYSBjaGFydCBjb21wb25lbnQuIFB1cmUgcmVuZGVyaW5nOiB0aGUgcGFyZW50IG93bnMgdGhlXG4vLyBkYXRhIGZldGNoOyB0aGlzIG1hcHMgKGNvbXBvbmVudCBjb25maWcsIHJvd3MsIHRoZSBvcGVuIHJlcG9ydCdzIGZvcm1hdFxuLy8gY29udGV4dCkg4oaSIG9wdGlvbiBhbmQgZXhwb3NlcyB0aGUgY2hhcnQgaW5zdGFuY2UgZm9yIHJlc2l6ZSArIHRoZSBmdXR1cmVcbi8vIGltYWdlIGV4cG9ydHMgKGdldERhdGFVUkwgbmVlZHMgdGhlIGNhbnZhcyByZW5kZXJlciByZWdpc3RlcmVkIGluXG4vLyB0aGVtZS9lY2hhcnRzLnRzKS4gVGhlIHNoYXJlZCBub3RlIHVuZGVyIHRoZSBwbG90IG5hbWVzIHRoZSBkYXlzIGFcbi8vIGNvbXBhcmlzb24gY29tcGFyZXMgYW5kIHdoeSB3aW5kb3dlZCBzZXJpZXMgcmVhZCBhcyB0aGV5IGRvOyB0aGUgc2VyaWVzJ1xuLy8gdG9vbHRpcHMgZGVzY3JpYmUgdGhlIHdpbmRvd3MgdGhlbXNlbHZlcy5cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ2FwcC1yZXBvcnQtY29tcG9uZW50LWNoYXJ0JyxcbiAgdGVtcGxhdGVVcmw6ICcuL2NvbXBvbmVudC1jaGFydC5jb21wb25lbnQuaHRtbCcsXG4gIHN0eWxlVXJsczogWycuL2NvbXBvbmVudC1jaGFydC5jb21wb25lbnQuc2NzcyddLFxuICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1xuICAgIENvbXBvbmVudE5vdGVDb21wb25lbnQsXG4gICAgTmd4RWNoYXJ0c0RpcmVjdGl2ZSxcbiAgXSxcbn0pXG5leHBvcnQgY2xhc3MgQ29tcG9uZW50Q2hhcnRDb21wb25lbnQge1xuXG4gIHB1YmxpYyByZWFkb25seSBjb21wb25lbnQgPSBpbnB1dC5yZXF1aXJlZDxSZXBvcnRDb21wb25lbnQ+KCk7XG4gIHB1YmxpYyByZWFkb25seSBkYXRhID0gaW5wdXQ8Q29tcG9uZW50RGF0YSB8IG51bGw+KG51bGwpO1xuXG4gIHB1YmxpYyByZWFkb25seSBvcHRpb25zID0gY29tcHV0ZWQ8RUNoYXJ0c09wdGlvbiB8IG51bGw+KCgpID0+IHtcbiAgICBjb25zdCBkYXRhID0gdGhpcy5kYXRhKCk7XG5cbiAgICByZXR1cm4gZGF0YVxuICAgICAgPyBidWlsZENoYXJ0T3B0aW9uKHRoaXMuY29tcG9uZW50KCksIGRhdGEsIHRoaXMuX2ZpbHRlclN0YXRlLmZvcm1hdENvbnRleHQoKSlcbiAgICAgIDogbnVsbDtcbiAgfSk7XG5cbiAgcHJpdmF0ZSBfY2hhcnQ6IEVDaGFydHMgfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSByZWFkb25seSBfZmlsdGVyU3RhdGUgPSBpbmplY3QoUmVwb3J0RmlsdGVyU3RhdGVTZXJ2aWNlKTtcblxuICBwdWJsaWMgb25DaGFydEluaXQoY2hhcnQ6IEVDaGFydHMpOiB2b2lkIHtcbiAgICB0aGlzLl9jaGFydCA9IGNoYXJ0O1xuICB9XG5cbiAgLy8gQ2FsbGVkIGJ5IHRoZSBjYW52YXMgd2hlbiB0aGUgYm94IGlzIHJlc2l6ZWQgaW4gZWRpdCBtb2RlLlxuICBwdWJsaWMgcmVzaXplKCk6IHZvaWQge1xuICAgIHRoaXMuX2NoYXJ0Py5yZXNpemUoKTtcbiAgfVxuXG4gIC8vIEhpZ2gtcmVzIGltYWdlIGZvciB0aGUgKGxhdGVyLXBoYXNlKSBQUFQvUERGIGV4cG9ydHMuXG4gIHB1YmxpYyBnZXREYXRhVVJMKHBpeGVsUmF0aW8gPSAzKTogc3RyaW5nIHwgbnVsbCB7XG4gICAgcmV0dXJuIHRoaXMuX2NoYXJ0Py5nZXREYXRhVVJMKHtcbiAgICAgIHR5cGU6ICdwbmcnLFxuICAgICAgcGl4ZWxSYXRpbyxcbiAgICAgIGJhY2tncm91bmRDb2xvcjogJyNmZmZmZmYnLFxuICAgIH0pID8/IG51bGw7XG4gIH1cbn1cbiIsIjxkaXZcbiAgY2xhc3M9XCJjaGFydC1ob3N0XCJcbiAgZWNoYXJ0c1xuICBbb3B0aW9uc109XCJvcHRpb25zKCkgPz8ge31cIlxuICBbdGhlbWVdPVwiJ3JlcG9ydCdcIlxuICBbYXV0b1Jlc2l6ZV09XCJ0cnVlXCJcbiAgKGNoYXJ0SW5pdCk9XCJvbkNoYXJ0SW5pdCgkZXZlbnQpXCI+XG48L2Rpdj5cbjxhcHAtcmVwb3J0LWNvbXBvbmVudC1ub3RlIFtkYXRhXT1cImRhdGEoKVwiPjwvYXBwLXJlcG9ydC1jb21wb25lbnQtbm90ZT5cbiJdfQ==
@@ -1,30 +1,63 @@
1
1
  import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
2
- import { formatNumber } from '../../format';
2
+ import { ReportChangePolarity } from '../../enums/report-change-polarity.enum';
3
+ import { ReportKpiTrend } from '../../enums/report-kpi-trend.enum';
4
+ import { kpiCardView } from '../../kpi-card';
5
+ import { componentNoteLines } from '../../output-notes';
3
6
  import { ReportFilterStateService } from '../../services/report-filter-state.service';
7
+ import { REPORT_KPI_CHANGE_COLORS, REPORT_KPI_SPARKLINE_COLOR } from '../../theme/palette';
8
+ import { REPORT_KPI_VALUE_FONT_SIZE } from '../../theme/typography';
9
+ import { DEFAULT_TEXT_COLOR } from '../../text';
10
+ import { ComponentNoteComponent } from '../component-note/component-note.component';
11
+ import { SparklineComponent } from './components/sparkline/sparkline.component';
4
12
  import * as i0 from "@angular/core";
5
13
  // A single big number: the component's measure aggregated over all (filtered)
6
- // rows server-side; this just formats the one cell, in the open report's
7
- // format context (its locale, its currency, and whether a percent arrives as a
8
- // 0-1 ratio or, from an older server, already x100).
14
+ // rows server-side, in the open report's format context (its locale, its
15
+ // currency, and whether a percent arrives as a 0-1 ratio or, from an older
16
+ // server, already x100). With a comparison, a target or a sparkline the server
17
+ // sends every other number the card shows too (ComponentData.kpi); kpi-card.ts
18
+ // decides what each line says, and this only lays them out, in order: the
19
+ // value and its label, the delta, the days, the target and the trend, then the
20
+ // note.
21
+ //
22
+ // The palette's colors and the value's size arrive as custom properties from
23
+ // the same constants the exports draw with. A host page overrides a color by
24
+ // setting --report-kpi-good-color, --report-kpi-bad-color,
25
+ // --report-kpi-neutral-color or --report-kpi-sparkline-color around the report.
9
26
  export class ComponentKpiComponent {
10
27
  component = input.required();
11
28
  data = input(null);
12
- label = computed(() => this._config().measure?.label ?? '');
13
- // A missing row or value reads '—'. A measure without number-format keys
14
- // reads as a plain number in the report's locale.
15
- value = computed(() => {
16
- const row = this.data()?.rows?.[0];
17
- const measure = this._config().measure;
18
- const column = measure?.column ?? Object.keys(row ?? {})[0];
19
- return formatNumber(row?.[column], measure, this._filterState.formatContext(), { empty: '—' });
20
- });
21
- _config = computed(() => (this.component().config ?? {}));
29
+ ReportKpiTrend = ReportKpiTrend;
30
+ tokens = {
31
+ valueSize: `${REPORT_KPI_VALUE_FONT_SIZE}px`,
32
+ text: DEFAULT_TEXT_COLOR,
33
+ good: `#${REPORT_KPI_CHANGE_COLORS[ReportChangePolarity.Good]}`,
34
+ bad: `#${REPORT_KPI_CHANGE_COLORS[ReportChangePolarity.Bad]}`,
35
+ neutral: `#${REPORT_KPI_CHANGE_COLORS[ReportChangePolarity.Neutral]}`,
36
+ sparkline: `#${REPORT_KPI_SPARKLINE_COLOR}`,
37
+ };
38
+ // Read off the component on every data change rather than through a memo of
39
+ // the config: a settings save repaints the same component object with new
40
+ // KPI keys, then reloads its data.
41
+ view = computed(() => kpiCardView((this.component().config ?? {}), this.data(), { format: this._filterState.formatContext(), timezone: this._filterState.timezone() }));
42
+ // The note sits under the card only when it has something to say, so an
43
+ // empty one takes no room.
44
+ hasNote = computed(() => componentNoteLines(this.data(), { locale: this._filterState.formatContext().locale, timezone: this._filterState.timezone() }, { windows: false, windowDescriptions: true }).length > 0);
22
45
  _filterState = inject(ReportFilterStateService);
23
46
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentKpiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
24
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentKpiComponent, isStandalone: true, selector: "app-report-component-kpi", inputs: { component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"kpi\">\n <div class=\"kpi-value\">{{ value() }}</div>\n @if (label()) {\n <div class=\"kpi-label\">{{ label() }}</div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.kpi{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px}.kpi .kpi-value{font-size:34px;font-weight:600;color:#1f2933;line-height:1}.kpi .kpi-label{font-size:12px;color:#7b8794}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
47
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentKpiComponent, isStandalone: true, selector: "app-report-component-kpi", inputs: { component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--report-kpi-value-size": "tokens.valueSize", "style.--report-kpi-text-default": "tokens.text", "style.--report-kpi-good-default": "tokens.good", "style.--report-kpi-bad-default": "tokens.bad", "style.--report-kpi-neutral-default": "tokens.neutral", "style.--report-kpi-sparkline-default": "tokens.sparkline" } }, ngImport: i0, template: "@let card = view();\n<div class=\"kpi fs-column.gap-xs.align-center.justify-center\">\n <div class=\"kpi-value\">{{ card.value }}</div>\n @if (card.label) {\n <div class=\"kpi-label small\">{{ card.label }}</div>\n }\n @if (card.delta; as delta) {\n <div class=\"kpi-delta fs-row.gap-xs.align-baseline.justify-center.wrap\">\n <span\n class=\"kpi-change\"\n [attr.data-tone]=\"delta.tone\">\n @if (delta.trend !== ReportKpiTrend.Flat) {\n <svg\n class=\"kpi-arrow\"\n [class.down]=\"delta.trend === ReportKpiTrend.Down\"\n viewBox=\"0 0 10 10\">\n <polygon points=\"5,1 9.5,9 0.5,9\"></polygon>\n </svg>\n }\n {{ delta.text }}\n </span>\n @if (delta.compared) {\n <span class=\"small\">{{ delta.compared }}</span>\n }\n </div>\n }\n @if (card.window) {\n <div class=\"kpi-line small\">{{ card.window }}</div>\n }\n @if (card.target; as target) {\n <div class=\"kpi-target fs-column.gap-xs.align-center\">\n <div class=\"kpi-line small\">\n {{ target.text }}\n @if (target.progress) {\n \u00B7 {{ target.progress }}\n }\n </div>\n @if (target.fill !== null) {\n <div\n class=\"kpi-progress\"\n [attr.data-tone]=\"target.tone\">\n <div\n class=\"kpi-progress-fill\"\n [style.width.%]=\"target.fill * 100\">\n </div>\n </div>\n }\n </div>\n }\n @if (card.sparkline; as sparkline) {\n <app-component-kpi-sparkline\n class=\"kpi-sparkline\"\n [values]=\"sparkline.values\"\n [partial]=\"sparkline.partial\">\n </app-component-kpi-sparkline>\n }\n <!-- The card prints its own days, so the note leaves them out. A KPI has no\n tooltip, so its note also says what a windowed value is. -->\n @if (hasNote()) {\n <app-report-component-note\n [data]=\"data()\"\n [windows]=\"false\"\n [windowDescriptions]=\"true\">\n </app-report-component-note>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%;--report-kpi-good: var(--report-kpi-good-color, var(--report-kpi-good-default));--report-kpi-bad: var(--report-kpi-bad-color, var(--report-kpi-bad-default));--report-kpi-neutral: var(--report-kpi-neutral-color, var(--report-kpi-neutral-default));--report-kpi-sparkline: var(--report-kpi-sparkline-color, var(--report-kpi-sparkline-default))}.kpi{width:100%;height:100%;overflow:hidden;text-align:center}.kpi .kpi-value{font-size:var(--report-kpi-value-size);font-weight:600;color:var(--report-kpi-text-default);line-height:1}.kpi .kpi-line{max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.kpi-change{font-weight:600;white-space:nowrap}.kpi-change[data-tone=good]{color:var(--report-kpi-good)}.kpi-change[data-tone=bad]{color:var(--report-kpi-bad)}.kpi-change[data-tone=neutral]{color:var(--report-kpi-neutral)}.kpi-arrow{width:.7em;height:.7em;fill:currentColor}.kpi-arrow.down{transform:rotate(180deg)}.kpi-delta,.kpi-target{max-width:100%}.kpi-progress{position:relative;width:120px;max-width:100%;height:4px;border-radius:2px;overflow:hidden;color:var(--report-kpi-neutral)}.kpi-progress[data-tone=good]{color:var(--report-kpi-good)}.kpi-progress:before{content:\"\";position:absolute;inset:0;background:currentColor;opacity:.2}.kpi-progress .kpi-progress-fill{position:relative;height:100%;background:currentColor}.kpi-sparkline{max-width:240px;flex:0 0 auto}\n"], dependencies: [{ kind: "component", type: ComponentNoteComponent, selector: "app-report-component-note", inputs: ["data", "windows", "windowDescriptions", "windowNotes"] }, { kind: "component", type: SparklineComponent, selector: "app-component-kpi-sparkline", inputs: ["values", "partial"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
25
48
  }
26
49
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentKpiComponent, decorators: [{
27
50
  type: Component,
28
- args: [{ selector: 'app-report-component-kpi', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"kpi\">\n <div class=\"kpi-value\">{{ value() }}</div>\n @if (label()) {\n <div class=\"kpi-label\">{{ label() }}</div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.kpi{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px}.kpi .kpi-value{font-size:34px;font-weight:600;color:#1f2933;line-height:1}.kpi .kpi-label{font-size:12px;color:#7b8794}\n"] }]
51
+ args: [{ selector: 'app-report-component-kpi', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
52
+ ComponentNoteComponent,
53
+ SparklineComponent,
54
+ ], host: {
55
+ '[style.--report-kpi-value-size]': 'tokens.valueSize',
56
+ '[style.--report-kpi-text-default]': 'tokens.text',
57
+ '[style.--report-kpi-good-default]': 'tokens.good',
58
+ '[style.--report-kpi-bad-default]': 'tokens.bad',
59
+ '[style.--report-kpi-neutral-default]': 'tokens.neutral',
60
+ '[style.--report-kpi-sparkline-default]': 'tokens.sparkline',
61
+ }, template: "@let card = view();\n<div class=\"kpi fs-column.gap-xs.align-center.justify-center\">\n <div class=\"kpi-value\">{{ card.value }}</div>\n @if (card.label) {\n <div class=\"kpi-label small\">{{ card.label }}</div>\n }\n @if (card.delta; as delta) {\n <div class=\"kpi-delta fs-row.gap-xs.align-baseline.justify-center.wrap\">\n <span\n class=\"kpi-change\"\n [attr.data-tone]=\"delta.tone\">\n @if (delta.trend !== ReportKpiTrend.Flat) {\n <svg\n class=\"kpi-arrow\"\n [class.down]=\"delta.trend === ReportKpiTrend.Down\"\n viewBox=\"0 0 10 10\">\n <polygon points=\"5,1 9.5,9 0.5,9\"></polygon>\n </svg>\n }\n {{ delta.text }}\n </span>\n @if (delta.compared) {\n <span class=\"small\">{{ delta.compared }}</span>\n }\n </div>\n }\n @if (card.window) {\n <div class=\"kpi-line small\">{{ card.window }}</div>\n }\n @if (card.target; as target) {\n <div class=\"kpi-target fs-column.gap-xs.align-center\">\n <div class=\"kpi-line small\">\n {{ target.text }}\n @if (target.progress) {\n \u00B7 {{ target.progress }}\n }\n </div>\n @if (target.fill !== null) {\n <div\n class=\"kpi-progress\"\n [attr.data-tone]=\"target.tone\">\n <div\n class=\"kpi-progress-fill\"\n [style.width.%]=\"target.fill * 100\">\n </div>\n </div>\n }\n </div>\n }\n @if (card.sparkline; as sparkline) {\n <app-component-kpi-sparkline\n class=\"kpi-sparkline\"\n [values]=\"sparkline.values\"\n [partial]=\"sparkline.partial\">\n </app-component-kpi-sparkline>\n }\n <!-- The card prints its own days, so the note leaves them out. A KPI has no\n tooltip, so its note also says what a windowed value is. -->\n @if (hasNote()) {\n <app-report-component-note\n [data]=\"data()\"\n [windows]=\"false\"\n [windowDescriptions]=\"true\">\n </app-report-component-note>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%;--report-kpi-good: var(--report-kpi-good-color, var(--report-kpi-good-default));--report-kpi-bad: var(--report-kpi-bad-color, var(--report-kpi-bad-default));--report-kpi-neutral: var(--report-kpi-neutral-color, var(--report-kpi-neutral-default));--report-kpi-sparkline: var(--report-kpi-sparkline-color, var(--report-kpi-sparkline-default))}.kpi{width:100%;height:100%;overflow:hidden;text-align:center}.kpi .kpi-value{font-size:var(--report-kpi-value-size);font-weight:600;color:var(--report-kpi-text-default);line-height:1}.kpi .kpi-line{max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.kpi-change{font-weight:600;white-space:nowrap}.kpi-change[data-tone=good]{color:var(--report-kpi-good)}.kpi-change[data-tone=bad]{color:var(--report-kpi-bad)}.kpi-change[data-tone=neutral]{color:var(--report-kpi-neutral)}.kpi-arrow{width:.7em;height:.7em;fill:currentColor}.kpi-arrow.down{transform:rotate(180deg)}.kpi-delta,.kpi-target{max-width:100%}.kpi-progress{position:relative;width:120px;max-width:100%;height:4px;border-radius:2px;overflow:hidden;color:var(--report-kpi-neutral)}.kpi-progress[data-tone=good]{color:var(--report-kpi-good)}.kpi-progress:before{content:\"\";position:absolute;inset:0;background:currentColor;opacity:.2}.kpi-progress .kpi-progress-fill{position:relative;height:100%;background:currentColor}.kpi-sparkline{max-width:240px;flex:0 0 auto}\n"] }]
29
62
  }] });
30
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcG9uZW50LWtwaS5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBwL3JlcG9ydHMvY29tcG9uZW50cy9jb21wb25lbnQta3BpL2NvbXBvbmVudC1rcGkuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9yZXBvcnRzL2NvbXBvbmVudHMvY29tcG9uZW50LWtwaS9jb21wb25lbnQta3BpLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFNUYsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUU1QyxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQzs7QUFHdEYsOEVBQThFO0FBQzlFLHlFQUF5RTtBQUN6RSwrRUFBK0U7QUFDL0UscURBQXFEO0FBUXJELE1BQU0sT0FBTyxxQkFBcUI7SUFFaEIsU0FBUyxHQUFHLEtBQUssQ0FBQyxRQUFRLEVBQW1CLENBQUM7SUFDOUMsSUFBSSxHQUFHLEtBQUssQ0FBdUIsSUFBSSxDQUFDLENBQUM7SUFFekMsS0FBSyxHQUFHLFFBQVEsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsT0FBTyxFQUFFLEtBQUssSUFBSSxFQUFFLENBQUMsQ0FBQztJQUU1RSx5RUFBeUU7SUFDekUsa0RBQWtEO0lBQ2xDLEtBQUssR0FBRyxRQUFRLENBQUMsR0FBRyxFQUFFO1FBQ3BDLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNuQyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsT0FBTyxDQUFDO1FBQ3ZDLE1BQU0sTUFBTSxHQUFHLE9BQU8sRUFBRSxNQUFNLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFNUQsT0FBTyxZQUFZLENBQUMsR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxZQUFZLENBQUMsYUFBYSxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNqRyxDQUFDLENBQUMsQ0FBQztJQUVjLE9BQU8sR0FBRyxRQUFRLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUMsTUFBTSxJQUFJLEVBQUUsQ0FBYyxDQUFDLENBQUM7SUFDdkUsWUFBWSxHQUFHLE1BQU0sQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO3dHQWxCdEQscUJBQXFCOzRGQUFyQixxQkFBcUIsd1ZDbEJsQyx5SkFNQTs7NEZEWWEscUJBQXFCO2tCQVBqQyxTQUFTOytCQUNFLDBCQUEwQixtQkFHbkIsdUJBQXVCLENBQUMsTUFBTSxjQUNuQyxJQUFJIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIENvbXBvbmVudCwgY29tcHV0ZWQsIGluamVjdCwgaW5wdXQgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHsgZm9ybWF0TnVtYmVyIH0gZnJvbSAnLi4vLi4vZm9ybWF0JztcbmltcG9ydCB7IENvbXBvbmVudERhdGEsIEtwaUNvbmZpZywgUmVwb3J0Q29tcG9uZW50IH0gZnJvbSAnLi4vLi4vaW50ZXJmYWNlcy9yZXBvcnQuaW50ZXJmYWNlJztcbmltcG9ydCB7IFJlcG9ydEZpbHRlclN0YXRlU2VydmljZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3JlcG9ydC1maWx0ZXItc3RhdGUuc2VydmljZSc7XG5cblxuLy8gQSBzaW5nbGUgYmlnIG51bWJlcjogdGhlIGNvbXBvbmVudCdzIG1lYXN1cmUgYWdncmVnYXRlZCBvdmVyIGFsbCAoZmlsdGVyZWQpXG4vLyByb3dzIHNlcnZlci1zaWRlOyB0aGlzIGp1c3QgZm9ybWF0cyB0aGUgb25lIGNlbGwsIGluIHRoZSBvcGVuIHJlcG9ydCdzXG4vLyBmb3JtYXQgY29udGV4dCAoaXRzIGxvY2FsZSwgaXRzIGN1cnJlbmN5LCBhbmQgd2hldGhlciBhIHBlcmNlbnQgYXJyaXZlcyBhcyBhXG4vLyAwLTEgcmF0aW8gb3IsIGZyb20gYW4gb2xkZXIgc2VydmVyLCBhbHJlYWR5IHgxMDApLlxuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnYXBwLXJlcG9ydC1jb21wb25lbnQta3BpJyxcbiAgdGVtcGxhdGVVcmw6ICcuL2NvbXBvbmVudC1rcGkuY29tcG9uZW50Lmh0bWwnLFxuICBzdHlsZVVybHM6IFsnLi9jb21wb25lbnQta3BpLmNvbXBvbmVudC5zY3NzJ10sXG4gIGNoYW5nZURldGVjdGlvbjogQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kuT25QdXNoLFxuICBzdGFuZGFsb25lOiB0cnVlLFxufSlcbmV4cG9ydCBjbGFzcyBDb21wb25lbnRLcGlDb21wb25lbnQge1xuXG4gIHB1YmxpYyByZWFkb25seSBjb21wb25lbnQgPSBpbnB1dC5yZXF1aXJlZDxSZXBvcnRDb21wb25lbnQ+KCk7XG4gIHB1YmxpYyByZWFkb25seSBkYXRhID0gaW5wdXQ8Q29tcG9uZW50RGF0YSB8IG51bGw+KG51bGwpO1xuXG4gIHB1YmxpYyByZWFkb25seSBsYWJlbCA9IGNvbXB1dGVkKCgpID0+IHRoaXMuX2NvbmZpZygpLm1lYXN1cmU/LmxhYmVsID8/ICcnKTtcblxuICAvLyBBIG1pc3Npbmcgcm93IG9yIHZhbHVlIHJlYWRzICfigJQnLiBBIG1lYXN1cmUgd2l0aG91dCBudW1iZXItZm9ybWF0IGtleXNcbiAgLy8gcmVhZHMgYXMgYSBwbGFpbiBudW1iZXIgaW4gdGhlIHJlcG9ydCdzIGxvY2FsZS5cbiAgcHVibGljIHJlYWRvbmx5IHZhbHVlID0gY29tcHV0ZWQoKCkgPT4ge1xuICAgIGNvbnN0IHJvdyA9IHRoaXMuZGF0YSgpPy5yb3dzPy5bMF07XG4gICAgY29uc3QgbWVhc3VyZSA9IHRoaXMuX2NvbmZpZygpLm1lYXN1cmU7XG4gICAgY29uc3QgY29sdW1uID0gbWVhc3VyZT8uY29sdW1uID8/IE9iamVjdC5rZXlzKHJvdyA/PyB7fSlbMF07XG5cbiAgICByZXR1cm4gZm9ybWF0TnVtYmVyKHJvdz8uW2NvbHVtbl0sIG1lYXN1cmUsIHRoaXMuX2ZpbHRlclN0YXRlLmZvcm1hdENvbnRleHQoKSwgeyBlbXB0eTogJ+KAlCcgfSk7XG4gIH0pO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgX2NvbmZpZyA9IGNvbXB1dGVkKCgpID0+ICh0aGlzLmNvbXBvbmVudCgpLmNvbmZpZyA/PyB7fSkgYXMgS3BpQ29uZmlnKTtcbiAgcHJpdmF0ZSByZWFkb25seSBfZmlsdGVyU3RhdGUgPSBpbmplY3QoUmVwb3J0RmlsdGVyU3RhdGVTZXJ2aWNlKTtcbn1cbiIsIjxkaXYgY2xhc3M9XCJrcGlcIj5cbiAgPGRpdiBjbGFzcz1cImtwaS12YWx1ZVwiPnt7IHZhbHVlKCkgfX08L2Rpdj5cbiAgQGlmIChsYWJlbCgpKSB7XG4gICAgPGRpdiBjbGFzcz1cImtwaS1sYWJlbFwiPnt7IGxhYmVsKCkgfX08L2Rpdj5cbiAgfVxuPC9kaXY+XG4iXX0=
63
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcG9uZW50LWtwaS5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBwL3JlcG9ydHMvY29tcG9uZW50cy9jb21wb25lbnQta3BpL2NvbXBvbmVudC1rcGkuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9yZXBvcnRzL2NvbXBvbmVudHMvY29tcG9uZW50LWtwaS9jb21wb25lbnQta3BpLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFNUYsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDL0UsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBRW5FLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUM3QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUN4RCxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQztBQUN0RixPQUFPLEVBQUUsd0JBQXdCLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUMzRixPQUFPLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNwRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDaEQsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sNENBQTRDLENBQUM7QUFFcEYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNENBQTRDLENBQUM7O0FBR2hGLDhFQUE4RTtBQUM5RSx5RUFBeUU7QUFDekUsMkVBQTJFO0FBQzNFLCtFQUErRTtBQUMvRSwrRUFBK0U7QUFDL0UsMEVBQTBFO0FBQzFFLCtFQUErRTtBQUMvRSxRQUFRO0FBQ1IsRUFBRTtBQUNGLDZFQUE2RTtBQUM3RSw2RUFBNkU7QUFDN0UsMkRBQTJEO0FBQzNELGdGQUFnRjtBQW9CaEYsTUFBTSxPQUFPLHFCQUFxQjtJQUVoQixTQUFTLEdBQUcsS0FBSyxDQUFDLFFBQVEsRUFBbUIsQ0FBQztJQUM5QyxJQUFJLEdBQUcsS0FBSyxDQUF1QixJQUFJLENBQUMsQ0FBQztJQUV6QyxjQUFjLEdBQUcsY0FBYyxDQUFDO0lBRWhDLE1BQU0sR0FBRztRQUN2QixTQUFTLEVBQUUsR0FBRywwQkFBMEIsSUFBSTtRQUM1QyxJQUFJLEVBQUUsa0JBQWtCO1FBQ3hCLElBQUksRUFBRSxJQUFJLHdCQUF3QixDQUFDLG9CQUFvQixDQUFDLElBQUksQ0FBQyxFQUFFO1FBQy9ELEdBQUcsRUFBRSxJQUFJLHdCQUF3QixDQUFDLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxFQUFFO1FBQzdELE9BQU8sRUFBRSxJQUFJLHdCQUF3QixDQUFDLG9CQUFvQixDQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ3JFLFNBQVMsRUFBRSxJQUFJLDBCQUEwQixFQUFFO0tBQzVDLENBQUM7SUFFRiw0RUFBNEU7SUFDNUUsMEVBQTBFO0lBQzFFLG1DQUFtQztJQUNuQixJQUFJLEdBQUcsUUFBUSxDQUFDLEdBQUcsRUFBRSxDQUFDLFdBQVcsQ0FDL0MsQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUMsTUFBTSxJQUFJLEVBQUUsQ0FBYyxFQUM1QyxJQUFJLENBQUMsSUFBSSxFQUFFLEVBQ1gsRUFBRSxNQUFNLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxhQUFhLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUN0RixDQUFDLENBQUM7SUFFSCx3RUFBd0U7SUFDeEUsMkJBQTJCO0lBQ1gsT0FBTyxHQUFHLFFBQVEsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxrQkFBa0IsQ0FDekQsSUFBSSxDQUFDLElBQUksRUFBRSxFQUNYLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxZQUFZLENBQUMsYUFBYSxFQUFFLENBQUMsTUFBTSxFQUFFLFFBQVEsRUFBRSxJQUFJLENBQUMsWUFBWSxDQUFDLFFBQVEsRUFBRSxFQUFFLEVBQzVGLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxJQUFJLEVBQUUsQ0FDN0MsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFFRyxZQUFZLEdBQUcsTUFBTSxDQUFDLHdCQUF3QixDQUFDLENBQUM7d0dBakN0RCxxQkFBcUI7NEZBQXJCLHFCQUFxQiwrcUJDaERsQyw0aUVBa0VBLG05Q0Q5Qkksc0JBQXNCLHdJQUN0QixrQkFBa0I7OzRGQVdULHFCQUFxQjtrQkFuQmpDLFNBQVM7K0JBQ0UsMEJBQTBCLG1CQUduQix1QkFBdUIsQ0FBQyxNQUFNLGNBQ25DLElBQUksV0FDUDt3QkFDUCxzQkFBc0I7d0JBQ3RCLGtCQUFrQjtxQkFDbkIsUUFDSzt3QkFDSixpQ0FBaUMsRUFBRSxrQkFBa0I7d0JBQ3JELG1DQUFtQyxFQUFFLGFBQWE7d0JBQ2xELG1DQUFtQyxFQUFFLGFBQWE7d0JBQ2xELGtDQUFrQyxFQUFFLFlBQVk7d0JBQ2hELHNDQUFzQyxFQUFFLGdCQUFnQjt3QkFDeEQsd0NBQXdDLEVBQUUsa0JBQWtCO3FCQUM3RCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENoYW5nZURldGVjdGlvblN0cmF0ZWd5LCBDb21wb25lbnQsIGNvbXB1dGVkLCBpbmplY3QsIGlucHV0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5cbmltcG9ydCB7IFJlcG9ydENoYW5nZVBvbGFyaXR5IH0gZnJvbSAnLi4vLi4vZW51bXMvcmVwb3J0LWNoYW5nZS1wb2xhcml0eS5lbnVtJztcbmltcG9ydCB7IFJlcG9ydEtwaVRyZW5kIH0gZnJvbSAnLi4vLi4vZW51bXMvcmVwb3J0LWtwaS10cmVuZC5lbnVtJztcbmltcG9ydCB7IENvbXBvbmVudERhdGEsIEtwaUNvbmZpZywgUmVwb3J0Q29tcG9uZW50IH0gZnJvbSAnLi4vLi4vaW50ZXJmYWNlcy9yZXBvcnQuaW50ZXJmYWNlJztcbmltcG9ydCB7IGtwaUNhcmRWaWV3IH0gZnJvbSAnLi4vLi4va3BpLWNhcmQnO1xuaW1wb3J0IHsgY29tcG9uZW50Tm90ZUxpbmVzIH0gZnJvbSAnLi4vLi4vb3V0cHV0LW5vdGVzJztcbmltcG9ydCB7IFJlcG9ydEZpbHRlclN0YXRlU2VydmljZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3JlcG9ydC1maWx0ZXItc3RhdGUuc2VydmljZSc7XG5pbXBvcnQgeyBSRVBPUlRfS1BJX0NIQU5HRV9DT0xPUlMsIFJFUE9SVF9LUElfU1BBUktMSU5FX0NPTE9SIH0gZnJvbSAnLi4vLi4vdGhlbWUvcGFsZXR0ZSc7XG5pbXBvcnQgeyBSRVBPUlRfS1BJX1ZBTFVFX0ZPTlRfU0laRSB9IGZyb20gJy4uLy4uL3RoZW1lL3R5cG9ncmFwaHknO1xuaW1wb3J0IHsgREVGQVVMVF9URVhUX0NPTE9SIH0gZnJvbSAnLi4vLi4vdGV4dCc7XG5pbXBvcnQgeyBDb21wb25lbnROb3RlQ29tcG9uZW50IH0gZnJvbSAnLi4vY29tcG9uZW50LW5vdGUvY29tcG9uZW50LW5vdGUuY29tcG9uZW50JztcblxuaW1wb3J0IHsgU3BhcmtsaW5lQ29tcG9uZW50IH0gZnJvbSAnLi9jb21wb25lbnRzL3NwYXJrbGluZS9zcGFya2xpbmUuY29tcG9uZW50JztcblxuXG4vLyBBIHNpbmdsZSBiaWcgbnVtYmVyOiB0aGUgY29tcG9uZW50J3MgbWVhc3VyZSBhZ2dyZWdhdGVkIG92ZXIgYWxsIChmaWx0ZXJlZClcbi8vIHJvd3Mgc2VydmVyLXNpZGUsIGluIHRoZSBvcGVuIHJlcG9ydCdzIGZvcm1hdCBjb250ZXh0IChpdHMgbG9jYWxlLCBpdHNcbi8vIGN1cnJlbmN5LCBhbmQgd2hldGhlciBhIHBlcmNlbnQgYXJyaXZlcyBhcyBhIDAtMSByYXRpbyBvciwgZnJvbSBhbiBvbGRlclxuLy8gc2VydmVyLCBhbHJlYWR5IHgxMDApLiBXaXRoIGEgY29tcGFyaXNvbiwgYSB0YXJnZXQgb3IgYSBzcGFya2xpbmUgdGhlIHNlcnZlclxuLy8gc2VuZHMgZXZlcnkgb3RoZXIgbnVtYmVyIHRoZSBjYXJkIHNob3dzIHRvbyAoQ29tcG9uZW50RGF0YS5rcGkpOyBrcGktY2FyZC50c1xuLy8gZGVjaWRlcyB3aGF0IGVhY2ggbGluZSBzYXlzLCBhbmQgdGhpcyBvbmx5IGxheXMgdGhlbSBvdXQsIGluIG9yZGVyOiB0aGVcbi8vIHZhbHVlIGFuZCBpdHMgbGFiZWwsIHRoZSBkZWx0YSwgdGhlIGRheXMsIHRoZSB0YXJnZXQgYW5kIHRoZSB0cmVuZCwgdGhlbiB0aGVcbi8vIG5vdGUuXG4vL1xuLy8gVGhlIHBhbGV0dGUncyBjb2xvcnMgYW5kIHRoZSB2YWx1ZSdzIHNpemUgYXJyaXZlIGFzIGN1c3RvbSBwcm9wZXJ0aWVzIGZyb21cbi8vIHRoZSBzYW1lIGNvbnN0YW50cyB0aGUgZXhwb3J0cyBkcmF3IHdpdGguIEEgaG9zdCBwYWdlIG92ZXJyaWRlcyBhIGNvbG9yIGJ5XG4vLyBzZXR0aW5nIC0tcmVwb3J0LWtwaS1nb29kLWNvbG9yLCAtLXJlcG9ydC1rcGktYmFkLWNvbG9yLFxuLy8gLS1yZXBvcnQta3BpLW5ldXRyYWwtY29sb3Igb3IgLS1yZXBvcnQta3BpLXNwYXJrbGluZS1jb2xvciBhcm91bmQgdGhlIHJlcG9ydC5cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ2FwcC1yZXBvcnQtY29tcG9uZW50LWtwaScsXG4gIHRlbXBsYXRlVXJsOiAnLi9jb21wb25lbnQta3BpLmNvbXBvbmVudC5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vY29tcG9uZW50LWtwaS5jb21wb25lbnQuc2NzcyddLFxuICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1xuICAgIENvbXBvbmVudE5vdGVDb21wb25lbnQsXG4gICAgU3BhcmtsaW5lQ29tcG9uZW50LFxuICBdLFxuICBob3N0OiB7XG4gICAgJ1tzdHlsZS4tLXJlcG9ydC1rcGktdmFsdWUtc2l6ZV0nOiAndG9rZW5zLnZhbHVlU2l6ZScsXG4gICAgJ1tzdHlsZS4tLXJlcG9ydC1rcGktdGV4dC1kZWZhdWx0XSc6ICd0b2tlbnMudGV4dCcsXG4gICAgJ1tzdHlsZS4tLXJlcG9ydC1rcGktZ29vZC1kZWZhdWx0XSc6ICd0b2tlbnMuZ29vZCcsXG4gICAgJ1tzdHlsZS4tLXJlcG9ydC1rcGktYmFkLWRlZmF1bHRdJzogJ3Rva2Vucy5iYWQnLFxuICAgICdbc3R5bGUuLS1yZXBvcnQta3BpLW5ldXRyYWwtZGVmYXVsdF0nOiAndG9rZW5zLm5ldXRyYWwnLFxuICAgICdbc3R5bGUuLS1yZXBvcnQta3BpLXNwYXJrbGluZS1kZWZhdWx0XSc6ICd0b2tlbnMuc3BhcmtsaW5lJyxcbiAgfSxcbn0pXG5leHBvcnQgY2xhc3MgQ29tcG9uZW50S3BpQ29tcG9uZW50IHtcblxuICBwdWJsaWMgcmVhZG9ubHkgY29tcG9uZW50ID0gaW5wdXQucmVxdWlyZWQ8UmVwb3J0Q29tcG9uZW50PigpO1xuICBwdWJsaWMgcmVhZG9ubHkgZGF0YSA9IGlucHV0PENvbXBvbmVudERhdGEgfCBudWxsPihudWxsKTtcblxuICBwdWJsaWMgcmVhZG9ubHkgUmVwb3J0S3BpVHJlbmQgPSBSZXBvcnRLcGlUcmVuZDtcblxuICBwdWJsaWMgcmVhZG9ubHkgdG9rZW5zID0ge1xuICAgIHZhbHVlU2l6ZTogYCR7UkVQT1JUX0tQSV9WQUxVRV9GT05UX1NJWkV9cHhgLFxuICAgIHRleHQ6IERFRkFVTFRfVEVYVF9DT0xPUixcbiAgICBnb29kOiBgIyR7UkVQT1JUX0tQSV9DSEFOR0VfQ09MT1JTW1JlcG9ydENoYW5nZVBvbGFyaXR5Lkdvb2RdfWAsXG4gICAgYmFkOiBgIyR7UkVQT1JUX0tQSV9DSEFOR0VfQ09MT1JTW1JlcG9ydENoYW5nZVBvbGFyaXR5LkJhZF19YCxcbiAgICBuZXV0cmFsOiBgIyR7UkVQT1JUX0tQSV9DSEFOR0VfQ09MT1JTW1JlcG9ydENoYW5nZVBvbGFyaXR5Lk5ldXRyYWxdfWAsXG4gICAgc3BhcmtsaW5lOiBgIyR7UkVQT1JUX0tQSV9TUEFSS0xJTkVfQ09MT1J9YCxcbiAgfTtcblxuICAvLyBSZWFkIG9mZiB0aGUgY29tcG9uZW50IG9uIGV2ZXJ5IGRhdGEgY2hhbmdlIHJhdGhlciB0aGFuIHRocm91Z2ggYSBtZW1vIG9mXG4gIC8vIHRoZSBjb25maWc6IGEgc2V0dGluZ3Mgc2F2ZSByZXBhaW50cyB0aGUgc2FtZSBjb21wb25lbnQgb2JqZWN0IHdpdGggbmV3XG4gIC8vIEtQSSBrZXlzLCB0aGVuIHJlbG9hZHMgaXRzIGRhdGEuXG4gIHB1YmxpYyByZWFkb25seSB2aWV3ID0gY29tcHV0ZWQoKCkgPT4ga3BpQ2FyZFZpZXcoXG4gICAgKHRoaXMuY29tcG9uZW50KCkuY29uZmlnID8/IHt9KSBhcyBLcGlDb25maWcsXG4gICAgdGhpcy5kYXRhKCksXG4gICAgeyBmb3JtYXQ6IHRoaXMuX2ZpbHRlclN0YXRlLmZvcm1hdENvbnRleHQoKSwgdGltZXpvbmU6IHRoaXMuX2ZpbHRlclN0YXRlLnRpbWV6b25lKCkgfSxcbiAgKSk7XG5cbiAgLy8gVGhlIG5vdGUgc2l0cyB1bmRlciB0aGUgY2FyZCBvbmx5IHdoZW4gaXQgaGFzIHNvbWV0aGluZyB0byBzYXksIHNvIGFuXG4gIC8vIGVtcHR5IG9uZSB0YWtlcyBubyByb29tLlxuICBwdWJsaWMgcmVhZG9ubHkgaGFzTm90ZSA9IGNvbXB1dGVkKCgpID0+IGNvbXBvbmVudE5vdGVMaW5lcyhcbiAgICB0aGlzLmRhdGEoKSxcbiAgICB7IGxvY2FsZTogdGhpcy5fZmlsdGVyU3RhdGUuZm9ybWF0Q29udGV4dCgpLmxvY2FsZSwgdGltZXpvbmU6IHRoaXMuX2ZpbHRlclN0YXRlLnRpbWV6b25lKCkgfSxcbiAgICB7IHdpbmRvd3M6IGZhbHNlLCB3aW5kb3dEZXNjcmlwdGlvbnM6IHRydWUgfSxcbiAgKS5sZW5ndGggPiAwKTtcblxuICBwcml2YXRlIHJlYWRvbmx5IF9maWx0ZXJTdGF0ZSA9IGluamVjdChSZXBvcnRGaWx0ZXJTdGF0ZVNlcnZpY2UpO1xufVxuIiwiQGxldCBjYXJkID0gdmlldygpO1xuPGRpdiBjbGFzcz1cImtwaSBmcy1jb2x1bW4uZ2FwLXhzLmFsaWduLWNlbnRlci5qdXN0aWZ5LWNlbnRlclwiPlxuICA8ZGl2IGNsYXNzPVwia3BpLXZhbHVlXCI+e3sgY2FyZC52YWx1ZSB9fTwvZGl2PlxuICBAaWYgKGNhcmQubGFiZWwpIHtcbiAgICA8ZGl2IGNsYXNzPVwia3BpLWxhYmVsIHNtYWxsXCI+e3sgY2FyZC5sYWJlbCB9fTwvZGl2PlxuICB9XG4gIEBpZiAoY2FyZC5kZWx0YTsgYXMgZGVsdGEpIHtcbiAgICA8ZGl2IGNsYXNzPVwia3BpLWRlbHRhIGZzLXJvdy5nYXAteHMuYWxpZ24tYmFzZWxpbmUuanVzdGlmeS1jZW50ZXIud3JhcFwiPlxuICAgICAgPHNwYW5cbiAgICAgICAgICBjbGFzcz1cImtwaS1jaGFuZ2VcIlxuICAgICAgICAgIFthdHRyLmRhdGEtdG9uZV09XCJkZWx0YS50b25lXCI+XG4gICAgICAgIEBpZiAoZGVsdGEudHJlbmQgIT09IFJlcG9ydEtwaVRyZW5kLkZsYXQpIHtcbiAgICAgICAgICA8c3ZnXG4gICAgICAgICAgICAgIGNsYXNzPVwia3BpLWFycm93XCJcbiAgICAgICAgICAgICAgW2NsYXNzLmRvd25dPVwiZGVsdGEudHJlbmQgPT09IFJlcG9ydEtwaVRyZW5kLkRvd25cIlxuICAgICAgICAgICAgICB2aWV3Qm94PVwiMCAwIDEwIDEwXCI+XG4gICAgICAgICAgICA8cG9seWdvbiBwb2ludHM9XCI1LDEgOS41LDkgMC41LDlcIj48L3BvbHlnb24+XG4gICAgICAgICAgPC9zdmc+XG4gICAgICAgIH1cbiAgICAgICAge3sgZGVsdGEudGV4dCB9fVxuICAgICAgPC9zcGFuPlxuICAgICAgQGlmIChkZWx0YS5jb21wYXJlZCkge1xuICAgICAgICA8c3BhbiBjbGFzcz1cInNtYWxsXCI+e3sgZGVsdGEuY29tcGFyZWQgfX08L3NwYW4+XG4gICAgICB9XG4gICAgPC9kaXY+XG4gIH1cbiAgQGlmIChjYXJkLndpbmRvdykge1xuICAgIDxkaXYgY2xhc3M9XCJrcGktbGluZSBzbWFsbFwiPnt7IGNhcmQud2luZG93IH19PC9kaXY+XG4gIH1cbiAgQGlmIChjYXJkLnRhcmdldDsgYXMgdGFyZ2V0KSB7XG4gICAgPGRpdiBjbGFzcz1cImtwaS10YXJnZXQgZnMtY29sdW1uLmdhcC14cy5hbGlnbi1jZW50ZXJcIj5cbiAgICAgIDxkaXYgY2xhc3M9XCJrcGktbGluZSBzbWFsbFwiPlxuICAgICAgICB7eyB0YXJnZXQudGV4dCB9fVxuICAgICAgICBAaWYgKHRhcmdldC5wcm9ncmVzcykge1xuICAgICAgICAgIMK3IHt7IHRhcmdldC5wcm9ncmVzcyB9fVxuICAgICAgICB9XG4gICAgICA8L2Rpdj5cbiAgICAgIEBpZiAodGFyZ2V0LmZpbGwgIT09IG51bGwpIHtcbiAgICAgICAgPGRpdlxuICAgICAgICAgICAgY2xhc3M9XCJrcGktcHJvZ3Jlc3NcIlxuICAgICAgICAgICAgW2F0dHIuZGF0YS10b25lXT1cInRhcmdldC50b25lXCI+XG4gICAgICAgICAgPGRpdlxuICAgICAgICAgICAgY2xhc3M9XCJrcGktcHJvZ3Jlc3MtZmlsbFwiXG4gICAgICAgICAgICBbc3R5bGUud2lkdGguJV09XCJ0YXJnZXQuZmlsbCAqIDEwMFwiPlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICA8L2Rpdj5cbiAgICAgIH1cbiAgICA8L2Rpdj5cbiAgfVxuICBAaWYgKGNhcmQuc3BhcmtsaW5lOyBhcyBzcGFya2xpbmUpIHtcbiAgICA8YXBwLWNvbXBvbmVudC1rcGktc3BhcmtsaW5lXG4gICAgICBjbGFzcz1cImtwaS1zcGFya2xpbmVcIlxuICAgICAgW3ZhbHVlc109XCJzcGFya2xpbmUudmFsdWVzXCJcbiAgICAgIFtwYXJ0aWFsXT1cInNwYXJrbGluZS5wYXJ0aWFsXCI+XG4gICAgPC9hcHAtY29tcG9uZW50LWtwaS1zcGFya2xpbmU+XG4gIH1cbiAgPCEtLSBUaGUgY2FyZCBwcmludHMgaXRzIG93biBkYXlzLCBzbyB0aGUgbm90ZSBsZWF2ZXMgdGhlbSBvdXQuIEEgS1BJIGhhcyBub1xuICAgICAgIHRvb2x0aXAsIHNvIGl0cyBub3RlIGFsc28gc2F5cyB3aGF0IGEgd2luZG93ZWQgdmFsdWUgaXMuIC0tPlxuICBAaWYgKGhhc05vdGUoKSkge1xuICAgIDxhcHAtcmVwb3J0LWNvbXBvbmVudC1ub3RlXG4gICAgICBbZGF0YV09XCJkYXRhKClcIlxuICAgICAgW3dpbmRvd3NdPVwiZmFsc2VcIlxuICAgICAgW3dpbmRvd0Rlc2NyaXB0aW9uc109XCJ0cnVlXCI+XG4gICAgPC9hcHAtcmVwb3J0LWNvbXBvbmVudC1ub3RlPlxuICB9XG48L2Rpdj5cbiJdfQ==
@@ -0,0 +1,19 @@
1
+ import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2
+ import { kpiSparklineGeometry } from '../../../../kpi-card';
3
+ import * as i0 from "@angular/core";
4
+ // A KPI's trend over its last periods: a line that stretches to the card's
5
+ // width, broken where a period has no value, with the last point marked
6
+ // (hollow while its period is still running). The markers are drawn over the
7
+ // line rather than in it, so they stay round however the line is stretched.
8
+ export class SparklineComponent {
9
+ values = input([]);
10
+ partial = input(false);
11
+ geometry = computed(() => kpiSparklineGeometry(this.values(), this.partial()));
12
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SparklineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SparklineComponent, isStandalone: true, selector: "app-component-kpi-sparkline", inputs: { values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, partial: { classPropertyName: "partial", publicName: "partial", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<svg\n class=\"sparkline-lines\"\n viewBox=\"0 0 100 100\"\n preserveAspectRatio=\"none\">\n @for (line of geometry().lines; track $index) {\n <polyline [attr.points]=\"line\"></polyline>\n }\n</svg>\n@for (dot of geometry().dots; track $index) {\n <span\n class=\"sparkline-marker\"\n [style.left.%]=\"dot.x\"\n [style.top.%]=\"dot.y\">\n </span>\n}\n@if (geometry().last; as last) {\n <span\n class=\"sparkline-marker\"\n [class.hollow]=\"last.hollow\"\n [style.left.%]=\"last.x\"\n [style.top.%]=\"last.y\">\n </span>\n}\n", styles: [":host{display:block;position:relative;width:calc(100% - 6px);height:28px;margin:3px;color:var(--report-kpi-sparkline)}.sparkline-lines{display:block;width:100%;height:100%;overflow:visible}.sparkline-lines polyline{fill:none;stroke:currentColor;stroke-width:1.5;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke}.sparkline-marker{position:absolute;box-sizing:border-box;width:6px;height:6px;margin:-3px 0 0 -3px;border:1.5px solid currentColor;border-radius:50%;background:currentColor}.sparkline-marker.hollow{background:Canvas}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14
+ }
15
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SparklineComponent, decorators: [{
16
+ type: Component,
17
+ args: [{ selector: 'app-component-kpi-sparkline', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<svg\n class=\"sparkline-lines\"\n viewBox=\"0 0 100 100\"\n preserveAspectRatio=\"none\">\n @for (line of geometry().lines; track $index) {\n <polyline [attr.points]=\"line\"></polyline>\n }\n</svg>\n@for (dot of geometry().dots; track $index) {\n <span\n class=\"sparkline-marker\"\n [style.left.%]=\"dot.x\"\n [style.top.%]=\"dot.y\">\n </span>\n}\n@if (geometry().last; as last) {\n <span\n class=\"sparkline-marker\"\n [class.hollow]=\"last.hollow\"\n [style.left.%]=\"last.x\"\n [style.top.%]=\"last.y\">\n </span>\n}\n", styles: [":host{display:block;position:relative;width:calc(100% - 6px);height:28px;margin:3px;color:var(--report-kpi-sparkline)}.sparkline-lines{display:block;width:100%;height:100%;overflow:visible}.sparkline-lines polyline{fill:none;stroke:currentColor;stroke-width:1.5;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke}.sparkline-marker{position:absolute;box-sizing:border-box;width:6px;height:6px;margin:-3px 0 0 -3px;border:1.5px solid currentColor;border-radius:50%;background:currentColor}.sparkline-marker.hollow{background:Canvas}\n"] }]
18
+ }] });
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhcmtsaW5lLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3NyYy9hcHAvcmVwb3J0cy9jb21wb25lbnRzL2NvbXBvbmVudC1rcGkvY29tcG9uZW50cy9zcGFya2xpbmUvc3BhcmtsaW5lLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3NyYy9hcHAvcmVwb3J0cy9jb21wb25lbnRzL2NvbXBvbmVudC1rcGkvY29tcG9uZW50cy9zcGFya2xpbmUvc3BhcmtsaW5lLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUVwRixPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQzs7QUFHNUQsMkVBQTJFO0FBQzNFLHdFQUF3RTtBQUN4RSw2RUFBNkU7QUFDN0UsNEVBQTRFO0FBUTVFLE1BQU0sT0FBTyxrQkFBa0I7SUFFYixNQUFNLEdBQUcsS0FBSyxDQUFvQixFQUFFLENBQUMsQ0FBQztJQUN0QyxPQUFPLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBRXZCLFFBQVEsR0FBRyxRQUFRLENBQUMsR0FBRyxFQUFFLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxFQUFFLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUM7d0dBTHBGLGtCQUFrQjs0RkFBbEIsa0JBQWtCLDRWQ2hCL0Isc2pCQXVCQTs7NEZEUGEsa0JBQWtCO2tCQVA5QixTQUFTOytCQUNFLDZCQUE2QixtQkFHdEIsdUJBQXVCLENBQUMsTUFBTSxjQUNuQyxJQUFJIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIENvbXBvbmVudCwgY29tcHV0ZWQsIGlucHV0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5cbmltcG9ydCB7IGtwaVNwYXJrbGluZUdlb21ldHJ5IH0gZnJvbSAnLi4vLi4vLi4vLi4va3BpLWNhcmQnO1xuXG5cbi8vIEEgS1BJJ3MgdHJlbmQgb3ZlciBpdHMgbGFzdCBwZXJpb2RzOiBhIGxpbmUgdGhhdCBzdHJldGNoZXMgdG8gdGhlIGNhcmQnc1xuLy8gd2lkdGgsIGJyb2tlbiB3aGVyZSBhIHBlcmlvZCBoYXMgbm8gdmFsdWUsIHdpdGggdGhlIGxhc3QgcG9pbnQgbWFya2VkXG4vLyAoaG9sbG93IHdoaWxlIGl0cyBwZXJpb2QgaXMgc3RpbGwgcnVubmluZykuIFRoZSBtYXJrZXJzIGFyZSBkcmF3biBvdmVyIHRoZVxuLy8gbGluZSByYXRoZXIgdGhhbiBpbiBpdCwgc28gdGhleSBzdGF5IHJvdW5kIGhvd2V2ZXIgdGhlIGxpbmUgaXMgc3RyZXRjaGVkLlxuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnYXBwLWNvbXBvbmVudC1rcGktc3BhcmtsaW5lJyxcbiAgdGVtcGxhdGVVcmw6ICcuL3NwYXJrbGluZS5jb21wb25lbnQuaHRtbCcsXG4gIHN0eWxlVXJsczogWycuL3NwYXJrbGluZS5jb21wb25lbnQuc2NzcyddLFxuICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbn0pXG5leHBvcnQgY2xhc3MgU3BhcmtsaW5lQ29tcG9uZW50IHtcblxuICBwdWJsaWMgcmVhZG9ubHkgdmFsdWVzID0gaW5wdXQ8KG51bWJlciB8IG51bGwpW10+KFtdKTtcbiAgcHVibGljIHJlYWRvbmx5IHBhcnRpYWwgPSBpbnB1dChmYWxzZSk7XG5cbiAgcHVibGljIHJlYWRvbmx5IGdlb21ldHJ5ID0gY29tcHV0ZWQoKCkgPT4ga3BpU3BhcmtsaW5lR2VvbWV0cnkodGhpcy52YWx1ZXMoKSwgdGhpcy5wYXJ0aWFsKCkpKTtcbn1cbiIsIjxzdmdcbiAgICBjbGFzcz1cInNwYXJrbGluZS1saW5lc1wiXG4gICAgdmlld0JveD1cIjAgMCAxMDAgMTAwXCJcbiAgICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPVwibm9uZVwiPlxuICBAZm9yIChsaW5lIG9mIGdlb21ldHJ5KCkubGluZXM7IHRyYWNrICRpbmRleCkge1xuICAgIDxwb2x5bGluZSBbYXR0ci5wb2ludHNdPVwibGluZVwiPjwvcG9seWxpbmU+XG4gIH1cbjwvc3ZnPlxuQGZvciAoZG90IG9mIGdlb21ldHJ5KCkuZG90czsgdHJhY2sgJGluZGV4KSB7XG4gIDxzcGFuXG4gICAgY2xhc3M9XCJzcGFya2xpbmUtbWFya2VyXCJcbiAgICBbc3R5bGUubGVmdC4lXT1cImRvdC54XCJcbiAgICBbc3R5bGUudG9wLiVdPVwiZG90LnlcIj5cbiAgPC9zcGFuPlxufVxuQGlmIChnZW9tZXRyeSgpLmxhc3Q7IGFzIGxhc3QpIHtcbiAgPHNwYW5cbiAgICBjbGFzcz1cInNwYXJrbGluZS1tYXJrZXJcIlxuICAgIFtjbGFzcy5ob2xsb3ddPVwibGFzdC5ob2xsb3dcIlxuICAgIFtzdHlsZS5sZWZ0LiVdPVwibGFzdC54XCJcbiAgICBbc3R5bGUudG9wLiVdPVwibGFzdC55XCI+XG4gIDwvc3Bhbj5cbn1cbiJdfQ==