@firestitch/report 18.2.2 → 18.3.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 (35) hide show
  1. package/app/reports/components/component-text/component-text.component.d.ts +34 -0
  2. package/app/reports/components/report-canvas/report-canvas.component.d.ts +17 -5
  3. package/app/reports/components/report-component/report-component.component.d.ts +11 -1
  4. package/app/reports/data/report.data.d.ts +12 -1
  5. package/app/reports/dialogs/report-settings/report-settings.component.d.ts +3 -0
  6. package/app/reports/export/report-export-collector.service.d.ts +1 -0
  7. package/app/reports/export/report-pdf.service.d.ts +7 -0
  8. package/app/reports/export/report-pptx.service.d.ts +2 -0
  9. package/app/reports/interfaces/report.interface.d.ts +16 -2
  10. package/app/reports/layout.d.ts +1 -0
  11. package/app/reports/text.d.ts +29 -0
  12. package/app/reports/views/report/fs-ai-report.component.d.ts +3 -0
  13. package/app/reports/views/report/report.component.d.ts +3 -0
  14. package/app/reports/views/reports/fs-ai-reports.component.d.ts +1 -1
  15. package/esm2022/app/reports/components/component-text/component-text.component.mjs +186 -0
  16. package/esm2022/app/reports/components/report-canvas/report-canvas.component.mjs +128 -17
  17. package/esm2022/app/reports/components/report-component/report-component.component.mjs +71 -7
  18. package/esm2022/app/reports/data/report.data.mjs +20 -9
  19. package/esm2022/app/reports/dialogs/report-settings/report-settings.component.mjs +17 -5
  20. package/esm2022/app/reports/export/report-export-collector.service.mjs +12 -2
  21. package/esm2022/app/reports/export/report-pdf.service.mjs +156 -4
  22. package/esm2022/app/reports/export/report-pptx.service.mjs +76 -5
  23. package/esm2022/app/reports/interfaces/report.interface.mjs +1 -1
  24. package/esm2022/app/reports/layout.mjs +4 -1
  25. package/esm2022/app/reports/text.mjs +221 -0
  26. package/esm2022/app/reports/views/report/fs-ai-report.component.mjs +14 -3
  27. package/esm2022/app/reports/views/report/report.component.mjs +18 -3
  28. package/esm2022/app/reports/views/reports/fs-ai-reports.component.mjs +11 -5
  29. package/fesm2022/{firestitch-report-echarts-BLtDCank.mjs → firestitch-report-echarts-Dg5PjbjT.mjs} +2 -2
  30. package/fesm2022/{firestitch-report-echarts-BLtDCank.mjs.map → firestitch-report-echarts-Dg5PjbjT.mjs.map} +1 -1
  31. package/fesm2022/{firestitch-report-firestitch-report-eTeJyMPo.mjs → firestitch-report-firestitch-report-JORFp_xa.mjs} +925 -62
  32. package/fesm2022/firestitch-report-firestitch-report-JORFp_xa.mjs.map +1 -0
  33. package/fesm2022/firestitch-report.mjs +1 -1
  34. package/package.json +1 -1
  35. package/fesm2022/firestitch-report-firestitch-report-eTeJyMPo.mjs.map +0 -1
@@ -0,0 +1,34 @@
1
+ import { SafeHtml } from '@angular/platform-browser';
2
+ import { FsHtmlEditorConfig } from '@firestitch/html-editor';
3
+ import { TextTokenContext } from '../../text';
4
+ import * as i0 from "@angular/core";
5
+ export declare class ComponentTextComponent {
6
+ readonly html: import("@angular/core").InputSignal<string>;
7
+ readonly tokens: import("@angular/core").InputSignal<TextTokenContext>;
8
+ readonly editing: import("@angular/core").InputSignal<boolean>;
9
+ readonly saved: import("@angular/core").OutputEmitterRef<string>;
10
+ readonly editEnded: import("@angular/core").OutputEmitterRef<void>;
11
+ readonly typeScale: {
12
+ body: string;
13
+ h1: string;
14
+ h2: string;
15
+ h3: string;
16
+ color: string;
17
+ };
18
+ readonly editorConfig: FsHtmlEditorConfig;
19
+ readonly displayHtml: import("@angular/core").Signal<SafeHtml>;
20
+ draft: string;
21
+ private readonly _editor;
22
+ private readonly _sanitizer;
23
+ private readonly _host;
24
+ private readonly _destroyRef;
25
+ private _isOpen;
26
+ constructor();
27
+ onKeydown(event: KeyboardEvent): void;
28
+ private _onDocumentPress;
29
+ private _close;
30
+ private _syncDraft;
31
+ private _finish;
32
+ static ɵfac: i0.ɵɵFactoryDeclaration<ComponentTextComponent, never>;
33
+ static ɵcmp: i0.ɵɵComponentDeclaration<ComponentTextComponent, "app-report-component-text", never, { "html": { "alias": "html"; "required": false; "isSignal": true; }; "tokens": { "alias": "tokens"; "required": true; "isSignal": true; }; "editing": { "alias": "editing"; "required": false; "isSignal": true; }; }, { "saved": "saved"; "editEnded": "editEnded"; }, never, never, true, never>;
34
+ }
@@ -1,7 +1,8 @@
1
- import { AfterViewInit, EventEmitter, OnChanges } from '@angular/core';
1
+ import { AfterViewInit, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
2
2
  import { CdkDragDrop } from '@angular/cdk/drag-drop';
3
3
  import { FsZoomPanComponent } from '@firestitch/zoom-pan';
4
- import { ComponentSettings, Report, ReportComponent, ReportFilterGroup, ReportPage } from '../../interfaces/report.interface';
4
+ import { ComponentSettings, Report, ReportComponent, ReportFilterGroup, ReportHeaderFooter, ReportPage } from '../../interfaces/report.interface';
5
+ import { TextTokenContext } from '../../text';
5
6
  import * as i0 from "@angular/core";
6
7
  export declare const PX_PER_INCH = 96;
7
8
  export interface GuideLine {
@@ -32,7 +33,6 @@ export declare class ReportCanvasComponent implements OnChanges, AfterViewInit {
32
33
  pageId: number | null;
33
34
  componentSettings: EventEmitter<ReportComponent>;
34
35
  reportChanged: EventEmitter<void>;
35
- editDone: EventEmitter<void>;
36
36
  pageChanged: EventEmitter<number>;
37
37
  zoomPan: FsZoomPanComponent;
38
38
  private _renderedComponents;
@@ -42,6 +42,7 @@ export declare class ReportCanvasComponent implements OnChanges, AfterViewInit {
42
42
  readonly flowPadding = 0.25;
43
43
  activePageIndex: number;
44
44
  zoom: number;
45
+ readonly masterMode: import("@angular/core").WritableSignal<boolean>;
45
46
  groups: Map<number, ReportFilterGroup>;
46
47
  selectedComponentId: number | null;
47
48
  readonly snapper: CanvasSnapper;
@@ -51,13 +52,24 @@ export declare class ReportCanvasComponent implements OnChanges, AfterViewInit {
51
52
  private _cdRef;
52
53
  private _destroyRef;
53
54
  private _fitted;
55
+ private _tokens;
54
56
  private _previousPageCount;
55
57
  private _emittedPageId;
56
- ngOnChanges(): void;
58
+ ngOnChanges(changes: SimpleChanges): void;
57
59
  get headingSizePx(): number;
58
60
  ngAfterViewInit(): void;
59
61
  get isFlow(): boolean;
60
62
  get activePage(): ReportPage | null;
63
+ get bands(): ReportHeaderFooter;
64
+ get masterComponents(): ReportComponent[];
65
+ get isMasterShown(): boolean;
66
+ get isPageEditing(): boolean;
67
+ get isMasterEditing(): boolean;
68
+ get tokens(): TextTokenContext;
69
+ toggleMasterMode(): void;
70
+ addText(): void;
71
+ onComponentRemoved(componentId: number): void;
72
+ toggleMasterHidden(page: ReportPage): void;
61
73
  get flowComponents(): ReportComponent[];
62
74
  selectPage(index: number): void;
63
75
  addPage(): void;
@@ -91,5 +103,5 @@ export declare class ReportCanvasComponent implements OnChanges, AfterViewInit {
91
103
  private _renderGuides;
92
104
  private _clearGuides;
93
105
  static ɵfac: i0.ɵɵFactoryDeclaration<ReportCanvasComponent, never>;
94
- static ɵcmp: i0.ɵɵComponentDeclaration<ReportCanvasComponent, "app-report-canvas", never, { "report": { "alias": "report"; "required": false; }; "editMode": { "alias": "editMode"; "required": false; }; "pageId": { "alias": "pageId"; "required": false; }; }, { "componentSettings": "componentSettings"; "reportChanged": "reportChanged"; "editDone": "editDone"; "pageChanged": "pageChanged"; }, never, never, true, never>;
106
+ static ɵcmp: i0.ɵɵComponentDeclaration<ReportCanvasComponent, "app-report-canvas", never, { "report": { "alias": "report"; "required": false; }; "editMode": { "alias": "editMode"; "required": false; }; "pageId": { "alias": "pageId"; "required": false; }; }, { "componentSettings": "componentSettings"; "reportChanged": "reportChanged"; "pageChanged": "pageChanged"; }, never, never, true, never>;
95
107
  }
@@ -2,6 +2,7 @@ import { EventEmitter, OnChanges, OnInit, SimpleChanges } from '@angular/core';
2
2
  import { FilterConfig } from '@firestitch/filter';
3
3
  import { ComponentData, ComponentSettings, ReportComponent as ReportComponentModel, ReportFilterGroup, ReportLayout } from '../../interfaces/report.interface';
4
4
  import { ComponentChartComponent } from '../component-chart/component-chart.component';
5
+ import { TextTokenContext } from '../../text';
5
6
  import { CanvasSnapper } from '../report-canvas/report-canvas.component';
6
7
  import * as i0 from "@angular/core";
7
8
  declare const HANDLES: readonly ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
@@ -25,6 +26,9 @@ export declare class ReportComponentComponent implements OnInit, OnChanges {
25
26
  clientY: number;
26
27
  }>;
27
28
  openSettings: EventEmitter<ReportComponentModel>;
29
+ readonly tokens: import("@angular/core").InputSignal<TextTokenContext>;
30
+ readonly removed: import("@angular/core").OutputEmitterRef<number>;
31
+ readonly textEditing: import("@angular/core").WritableSignal<boolean>;
28
32
  chartComponent: ComponentChartComponent;
29
33
  get resizeHandles(): readonly Handle[];
30
34
  filterConfig: FilterConfig;
@@ -35,6 +39,8 @@ export declare class ReportComponentComponent implements OnInit, OnChanges {
35
39
  private _reportData;
36
40
  private _filterState;
37
41
  private _process;
42
+ private _prompt;
43
+ private _lastPress;
38
44
  private _destroyRef;
39
45
  private _cdRef;
40
46
  private _zone;
@@ -42,6 +48,10 @@ export declare class ReportComponentComponent implements OnInit, OnChanges {
42
48
  ngOnInit(): void;
43
49
  ngOnChanges(changes: SimpleChanges): void;
44
50
  get filterKey(): string;
51
+ get isText(): boolean;
52
+ editText(): void;
53
+ onTextSaved(html: string): void;
54
+ deleteText(): void;
45
55
  get truncated(): boolean;
46
56
  get bodyPadding(): string;
47
57
  get componentFilterGroups(): ReportFilterGroup[];
@@ -67,6 +77,6 @@ export declare class ReportComponentComponent implements OnInit, OnChanges {
67
77
  private _applyGeometry;
68
78
  private _load;
69
79
  static ɵfac: i0.ɵɵFactoryDeclaration<ReportComponentComponent, never>;
70
- static ɵcmp: i0.ɵɵComponentDeclaration<ReportComponentComponent, "app-report-component", never, { "reportId": { "alias": "reportId"; "required": false; }; "component": { "alias": "component"; "required": false; }; "groups": { "alias": "groups"; "required": false; }; "editMode": { "alias": "editMode"; "required": false; }; "layout": { "alias": "layout"; "required": false; }; "zoom": { "alias": "zoom"; "required": false; }; "snapper": { "alias": "snapper"; "required": false; }; "selected": { "alias": "selected"; "required": false; }; }, { "selectComponent": "selectComponent"; "positionChanged": "positionChanged"; "flowReorder": "flowReorder"; "openSettings": "openSettings"; }, never, never, true, never>;
80
+ static ɵcmp: i0.ɵɵComponentDeclaration<ReportComponentComponent, "app-report-component", never, { "reportId": { "alias": "reportId"; "required": false; }; "component": { "alias": "component"; "required": false; }; "groups": { "alias": "groups"; "required": false; }; "editMode": { "alias": "editMode"; "required": false; }; "layout": { "alias": "layout"; "required": false; }; "zoom": { "alias": "zoom"; "required": false; }; "snapper": { "alias": "snapper"; "required": false; }; "selected": { "alias": "selected"; "required": false; }; "tokens": { "alias": "tokens"; "required": false; "isSignal": true; }; }, { "selectComponent": "selectComponent"; "positionChanged": "positionChanged"; "flowReorder": "flowReorder"; "openSettings": "openSettings"; "removed": "removed"; }, never, never, true, never>;
71
81
  }
72
82
  export {};
@@ -16,13 +16,24 @@ export declare class ReportData<T = any> {
16
16
  rename(reportId: number, name: string, config?: RequestConfig): Observable<T>;
17
17
  update(reportId: number, settings: Record<string, unknown>, config?: RequestConfig): Observable<T>;
18
18
  addPage(reportId: number, config?: RequestConfig): Observable<T>;
19
- updatePage(reportId: number, pageId: number, name: string, config?: RequestConfig): Observable<T>;
19
+ updatePage(reportId: number, pageId: number, page: {
20
+ name?: string;
21
+ masterHidden?: boolean;
22
+ }, config?: RequestConfig): Observable<T>;
20
23
  deletePage(reportId: number, pageId: number, config?: RequestConfig): Observable<T>;
21
24
  savePagePositions(reportId: number, positions: {
22
25
  pageId: number;
23
26
  order: number;
24
27
  }[], config?: RequestConfig): Observable<T>;
25
28
  updateComponent(reportId: number, componentId: number, settings: Record<string, unknown>, config?: RequestConfig): Observable<T>;
29
+ addText(reportId: number, text: {
30
+ pageId?: number;
31
+ html?: string;
32
+ x: number;
33
+ y: number;
34
+ w: number;
35
+ h: number;
36
+ }, config?: RequestConfig): Observable<T>;
26
37
  deleteComponent(reportId: number, componentId: number, config?: RequestConfig): Observable<T>;
27
38
  delete(reportId: number, config?: RequestConfig): Observable<T>;
28
39
  undelete(reportId: number, config?: RequestConfig): Observable<T>;
@@ -27,6 +27,9 @@ export declare class ReportSettingsComponent implements OnInit {
27
27
  pageHeight: number | string;
28
28
  readonly pageDimensionMin = 1;
29
29
  readonly pageDimensionMax = 50;
30
+ headerHeight: number | string;
31
+ footerHeight: number | string;
32
+ readonly headerFooterMax = 3;
30
33
  layout: 'freeform' | 'flow';
31
34
  timezone: string;
32
35
  headingSize: number;
@@ -15,6 +15,7 @@ export interface CollectedReport {
15
15
  filterSummary: string[];
16
16
  }
17
17
  export declare function emptyLabel(item: CollectedComponent): string;
18
+ export declare function isFailed(item: CollectedComponent): boolean;
18
19
  export declare function failedPageNames(collected: CollectedReport): string[];
19
20
  export declare class ReportExportCollectorService {
20
21
  private _reportData;
@@ -3,6 +3,13 @@ import * as i0 from "@angular/core";
3
3
  export declare class ReportPdfService {
4
4
  export(collected: CollectedReport): Promise<void>;
5
5
  private _titlePage;
6
+ private _renderText;
7
+ private _wrap;
8
+ private _drawWord;
9
+ private _font;
10
+ private _trailingSpace;
11
+ private _alignOffset;
12
+ private _tokens;
6
13
  private _renderComponent;
7
14
  private _renderTable;
8
15
  private _renderKpi;
@@ -3,6 +3,8 @@ import * as i0 from "@angular/core";
3
3
  export declare class ReportPptxService {
4
4
  export(collected: CollectedReport): Promise<void>;
5
5
  private _titleSlide;
6
+ private _renderText;
7
+ private _tokens;
6
8
  private _renderComponent;
7
9
  private _renderChart;
8
10
  private _renderTable;
@@ -8,11 +8,21 @@ export interface Report {
8
8
  pageWidth: number;
9
9
  pageHeight: number;
10
10
  pages: ReportPage[];
11
+ master: ReportMaster | null;
11
12
  filterGroups: ReportFilterGroup[];
12
13
  }
14
+ export interface ReportMaster {
15
+ id: number;
16
+ components: ReportComponent[];
17
+ }
18
+ export interface ReportHeaderFooter {
19
+ headerHeight: number;
20
+ footerHeight: number;
21
+ }
13
22
  export interface ReportConfig {
14
23
  styles?: ReportStyles;
15
24
  groupBy?: ReportGroupBy;
25
+ headerFooter?: ReportHeaderFooter;
16
26
  }
17
27
  export interface ReportGroupBy {
18
28
  enabled: boolean;
@@ -34,9 +44,10 @@ export interface ReportPage {
34
44
  id: number;
35
45
  name: string | null;
36
46
  order: number;
47
+ masterHidden: boolean;
37
48
  components: ReportComponent[];
38
49
  }
39
- export type ReportComponentType = 'chart' | 'list' | 'kpi';
50
+ export type ReportComponentType = 'chart' | 'list' | 'kpi' | 'text';
40
51
  export interface ReportComponent {
41
52
  id: number;
42
53
  type: ReportComponentType;
@@ -49,7 +60,7 @@ export interface ReportComponent {
49
60
  flowWidth: number;
50
61
  order: number;
51
62
  sql: string | null;
52
- config: ChartConfig & ListConfig & KpiConfig & ComponentLayoutConfig & Record<string, unknown>;
63
+ config: ChartConfig & ListConfig & KpiConfig & TextConfig & ComponentLayoutConfig & Record<string, unknown>;
53
64
  filters: ReportFilter[];
54
65
  }
55
66
  export interface ReportComponentSql {
@@ -59,6 +70,9 @@ export interface ReportComponentSql {
59
70
  error: string | null;
60
71
  errorType: string | null;
61
72
  }
73
+ export interface TextConfig {
74
+ html?: string;
75
+ }
62
76
  export interface ComponentLayoutConfig {
63
77
  padding?: ComponentPadding;
64
78
  }
@@ -11,3 +11,4 @@ export interface ComponentGeometry {
11
11
  export declare function pageGeometry(report: Report, page: ReportPage): Map<number, ComponentGeometry>;
12
12
  export declare const PAGE_DIMENSION_MIN = 1;
13
13
  export declare const PAGE_DIMENSION_MAX = 50;
14
+ export declare const HEADER_FOOTER_MAX = 3;
@@ -0,0 +1,29 @@
1
+ export declare const DEFAULT_TEXT_SIZE = 12;
2
+ export declare const HEADING_TEXT_SIZES: Record<number, number>;
3
+ export declare const DEFAULT_TEXT_COLOR = "#1f2933";
4
+ export interface TextTokenContext {
5
+ pageNumber: number;
6
+ pageName: string;
7
+ reportName: string;
8
+ }
9
+ export interface TextRun {
10
+ text: string;
11
+ bold?: boolean;
12
+ italic?: boolean;
13
+ underline?: boolean;
14
+ strike?: boolean;
15
+ color?: string;
16
+ highlight?: string;
17
+ fontSize?: number;
18
+ fontFace?: string;
19
+ link?: string;
20
+ }
21
+ export interface TextParagraph {
22
+ runs: TextRun[];
23
+ align?: 'left' | 'center' | 'right' | 'justify';
24
+ bullet?: 'bullet' | 'number';
25
+ heading?: number;
26
+ }
27
+ export declare function fillTextTokens(html: string, context: TextTokenContext): string;
28
+ export declare function textParagraphs(html: string, context: TextTokenContext): TextParagraph[];
29
+ export declare function textPlain(html: string, context: TextTokenContext): string;
@@ -30,6 +30,9 @@ export declare class FsAiReportComponent {
30
30
  private _zone;
31
31
  constructor();
32
32
  toggleEditMode(): void;
33
+ get isMasterMode(): boolean;
34
+ addText(): void;
35
+ toggleMasterMode(): void;
33
36
  reportSettings(): void;
34
37
  componentSettings(component: ReportComponentModel): void;
35
38
  onCanvasPageChanged(pageId: number | null): void;
@@ -29,6 +29,9 @@ export declare class ReportComponent {
29
29
  reportChange(report: ReportListItem | null): void;
30
30
  createReport(): void;
31
31
  toggleEditMode(): void;
32
+ get isMasterMode(): boolean;
33
+ addText(): void;
34
+ toggleMasterMode(): void;
32
35
  reportSettings(): void;
33
36
  componentSettings(component: ReportComponentModel): void;
34
37
  onCanvasReportChanged(): void;
@@ -25,7 +25,7 @@ export declare class FsAiReportsComponent implements OnInit {
25
25
  ngOnInit(): void;
26
26
  openLink(report: ReportListItem): unknown[];
27
27
  isDeleted(report: ReportListItem): boolean;
28
- createReport(): void;
28
+ createReport(reportFolderId?: number): void;
29
29
  openSettings(): void;
30
30
  private _initFilterConfig;
31
31
  private _initTreeConfig;
@@ -0,0 +1,186 @@
1
+ import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, computed, effect, inject, input, output, untracked, viewChild, } from '@angular/core';
2
+ import { FormsModule } from '@angular/forms';
3
+ import { DomSanitizer } from '@angular/platform-browser';
4
+ import { FsHtmlEditorComponent } from '@firestitch/html-editor';
5
+ import { DEFAULT_TEXT_COLOR, DEFAULT_TEXT_SIZE, HEADING_TEXT_SIZES, fillTextTokens, } from '../../text';
6
+ import * as i0 from "@angular/core";
7
+ import * as i1 from "@angular/forms";
8
+ // Point sizes — the report's type unit (1:1 with PowerPoint and PDF).
9
+ const FONT_SIZES = ['8', '9', '10', '11', '12', '14', '16', '18', '20', '24', '28', '32', '36', '40', '48', '60', '72'];
10
+ // The fields a block can carry, filled per page on the canvas and in exports.
11
+ const TOKENS = {
12
+ '{{page.number}}': 'Page number',
13
+ '{{page.name}}': 'Page name',
14
+ '{{report.name}}': 'Report name',
15
+ };
16
+ // What the editor may produce — exactly what the server keeps
17
+ // (ReportTextHtml) and the exports draw. Anything else would edit fine and
18
+ // then vanish on save.
19
+ const ALLOWED_TAGS = ['p', 'h1', 'h2', 'h3', 'br', 'strong', 'em', 'u', 's', 'span', 'a', 'ul', 'ol', 'li'];
20
+ const ALLOWED_STYLES = ['color', 'background-color', 'font-size', 'font-weight', 'font-style', 'text-decoration', 'font-family', 'text-align'];
21
+ // Froala's own toolbars and popups live on the body, not in the block; a press
22
+ // on any of them is still part of editing the block.
23
+ const EDITOR_CHROME = '.fr-toolbar, .fr-popup, .fr-dropdown-menu, .fr-modal, .fr-tooltip';
24
+ // A text block: authored rich text on the page.
25
+ //
26
+ // Read mode renders the stored html with its tokens ({{page.number}}, …) filled
27
+ // for the page it is drawn on. The html has already been rebuilt server-side
28
+ // from an allowlist (ReportTextHtml), which is what makes binding it as trusted
29
+ // markup safe — Angular's own sanitizer would strip the inline styles that ARE
30
+ // the formatting.
31
+ //
32
+ // Edit mode mounts <fs-html-editor> in its floating mode in place — the words
33
+ // stay where they sit on the slide — with Froala's inline toolbar popping up
34
+ // over them. The editor exists only while editing; the raw html is edited, so a
35
+ // token reads as {{page.number}} while editing and as the number otherwise.
36
+ //
37
+ // The block never saves on its own: it emits the new html when editing ends
38
+ // and the host persists it.
39
+ export class ComponentTextComponent {
40
+ // The block's stored html (config.html). Its own input rather than read off
41
+ // the component, so a saved edit — a new string on the same component — is
42
+ // seen as a change and repaints.
43
+ html = input('');
44
+ tokens = input.required();
45
+ editing = input(false);
46
+ // The html after an edit, only when it changed.
47
+ saved = output();
48
+ // A press landed outside the block (or Escape): the host ends edit mode,
49
+ // which saves.
50
+ editEnded = output();
51
+ typeScale = {
52
+ body: `${DEFAULT_TEXT_SIZE}pt`,
53
+ h1: `${HEADING_TEXT_SIZES[1]}pt`,
54
+ h2: `${HEADING_TEXT_SIZES[2]}pt`,
55
+ h3: `${HEADING_TEXT_SIZES[3]}pt`,
56
+ color: DEFAULT_TEXT_COLOR,
57
+ };
58
+ editorConfig = {
59
+ floating: true,
60
+ autofocus: true,
61
+ placeholder: 'Type here',
62
+ buttons: [
63
+ {
64
+ name: 'insertField',
65
+ title: 'Insert field',
66
+ svgKey: 'tags',
67
+ options: TOKENS,
68
+ click: (editor, value) => editor.html.insert(value),
69
+ },
70
+ ],
71
+ froalaConfig: {
72
+ toolbarButtons: [
73
+ 'paragraphFormat', 'fontSize', '|',
74
+ 'bold', 'italic', 'underline', 'strikeThrough', 'textColor', '|',
75
+ 'align', 'formatUL', 'formatOL', '|',
76
+ 'insertLink', 'insertField', 'clearFormatting',
77
+ ],
78
+ paragraphFormat: { N: 'Normal', H1: 'Heading 1', H2: 'Heading 2', H3: 'Heading 3' },
79
+ paragraphFormatSelection: true,
80
+ fontSize: FONT_SIZES,
81
+ fontSizeUnit: 'pt',
82
+ fontSizeSelection: true,
83
+ htmlAllowedTags: ALLOWED_TAGS,
84
+ htmlAllowedAttrs: ['style', 'href', 'target', 'rel'],
85
+ htmlAllowedStyleProps: ALLOWED_STYLES,
86
+ htmlRemoveTags: ['script', 'style'],
87
+ // Inline styles rather than Froala's fr-* classes, which neither the
88
+ // server nor the exports know.
89
+ useClasses: false,
90
+ pastePlain: true,
91
+ // Every keystroke reaches the model straight away, so ending the edit an
92
+ // instant after typing never loses the last word.
93
+ typingTimer: 0,
94
+ },
95
+ };
96
+ displayHtml = computed(() => this._sanitizer.bypassSecurityTrustHtml(fillTextTokens(this.html(), this.tokens())));
97
+ // What the editor holds right now.
98
+ draft = '';
99
+ _editor = viewChild(FsHtmlEditorComponent);
100
+ _sanitizer = inject(DomSanitizer);
101
+ _host = inject(ElementRef);
102
+ _destroyRef = inject(DestroyRef);
103
+ _isOpen = false;
104
+ constructor() {
105
+ // Opening seeds the draft; closing — whoever closed it: a press outside,
106
+ // Escape, the block being deselected or edit mode ending — saves it.
107
+ effect(() => {
108
+ const editing = this.editing();
109
+ untracked(() => {
110
+ if (editing && !this._isOpen) {
111
+ this._isOpen = true;
112
+ this.draft = this.html();
113
+ }
114
+ else if (!editing && this._isOpen) {
115
+ this._finish();
116
+ }
117
+ });
118
+ });
119
+ // A press anywhere that is not the block or the editor's own chrome ends
120
+ // the edit. Captured, so it is seen before the canvas or a sibling
121
+ // component acts on the same press.
122
+ const onPress = (event) => this._onDocumentPress(event);
123
+ document.addEventListener('pointerdown', onPress, true);
124
+ this._destroyRef.onDestroy(() => document.removeEventListener('pointerdown', onPress, true));
125
+ }
126
+ onKeydown(event) {
127
+ if (this.editing() && event.key === 'Escape') {
128
+ event.preventDefault();
129
+ this._close();
130
+ }
131
+ }
132
+ _onDocumentPress(event) {
133
+ if (!this.editing()) {
134
+ return;
135
+ }
136
+ const target = event.target;
137
+ if (target && (this._host.nativeElement.contains(target) || target.closest(EDITOR_CHROME))) {
138
+ return;
139
+ }
140
+ this._close();
141
+ }
142
+ // Save NOW, then ask the host to close. Saving here rather than waiting for
143
+ // the editing input to flip matters: the same press may be on a page tab,
144
+ // and by the time the input changed this block could already be destroyed,
145
+ // with its edit gone.
146
+ _close() {
147
+ this._syncDraft();
148
+ this._finish();
149
+ this.editEnded.emit();
150
+ }
151
+ // Read the editor directly rather than trusting the last model update.
152
+ _syncDraft() {
153
+ const html = this._editor()?.editor?.html?.get();
154
+ if (typeof html === 'string') {
155
+ this.draft = html;
156
+ }
157
+ }
158
+ _finish() {
159
+ if (!this._isOpen) {
160
+ return;
161
+ }
162
+ this._isOpen = false;
163
+ if (this.draft !== this.html()) {
164
+ this.saved.emit(this.draft);
165
+ }
166
+ }
167
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
168
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentTextComponent, isStandalone: true, selector: "app-report-component-text", inputs: { html: { classPropertyName: "html", publicName: "html", isSignal: true, isRequired: false, transformFunction: null }, tokens: { classPropertyName: "tokens", publicName: "tokens", isSignal: true, isRequired: true, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", editEnded: "editEnded" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "style.--text-size": "typeScale.body", "style.--text-h1-size": "typeScale.h1", "style.--text-h2-size": "typeScale.h2", "style.--text-h3-size": "typeScale.h3", "style.--text-color": "typeScale.color" } }, viewQueries: [{ propertyName: "_editor", first: true, predicate: FsHtmlEditorComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@if (editing()) {\n <!-- Floating fs-html-editor: no frame, Froala's inline toolbar pops up over\n the text. pointerdown stays here so a press in the text never starts a\n drag of the block or a pan of the canvas. -->\n <fs-html-editor\n class=\"text-content editing\"\n [config]=\"editorConfig\"\n [(ngModel)]=\"draft\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n </fs-html-editor>\n} @else {\n <div\n class=\"text-content\"\n [innerHTML]=\"displayHtml()\">\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;height:100%}.text-content{display:block;width:100%;height:100%;overflow:hidden;font-family:Arial,Helvetica,sans-serif;font-size:var(--text-size);line-height:1.25;color:var(--text-color);word-wrap:break-word}.text-content ::ng-deep p,.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3,.text-content ::ng-deep ul,.text-content ::ng-deep ol{margin:0}.text-content ::ng-deep h1{font-size:var(--text-h1-size)}.text-content ::ng-deep h2{font-size:var(--text-h2-size)}.text-content ::ng-deep h3{font-size:var(--text-h3-size)}.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3{font-weight:700;line-height:1.2}.text-content ::ng-deep ul,.text-content ::ng-deep ol{padding-left:1.4em}.text-content ::ng-deep p:empty:before{content:\"\\200b\"}.text-content ::ng-deep a{color:inherit}.text-content.editing{overflow:auto;cursor:text}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FsHtmlEditorComponent, selector: "fs-html-editor", inputs: ["config", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
169
+ }
170
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentTextComponent, decorators: [{
171
+ type: Component,
172
+ args: [{ selector: 'app-report-component-text', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
173
+ FormsModule,
174
+ FsHtmlEditorComponent,
175
+ ], host: {
176
+ '(keydown)': 'onKeydown($event)',
177
+ // The document type scale comes from the same constants the PowerPoint and
178
+ // PDF exports draw with, so a paragraph is one size in all three.
179
+ '[style.--text-size]': 'typeScale.body',
180
+ '[style.--text-h1-size]': 'typeScale.h1',
181
+ '[style.--text-h2-size]': 'typeScale.h2',
182
+ '[style.--text-h3-size]': 'typeScale.h3',
183
+ '[style.--text-color]': 'typeScale.color',
184
+ }, template: "@if (editing()) {\n <!-- Floating fs-html-editor: no frame, Froala's inline toolbar pops up over\n the text. pointerdown stays here so a press in the text never starts a\n drag of the block or a pan of the canvas. -->\n <fs-html-editor\n class=\"text-content editing\"\n [config]=\"editorConfig\"\n [(ngModel)]=\"draft\"\n (pointerdown)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\">\n </fs-html-editor>\n} @else {\n <div\n class=\"text-content\"\n [innerHTML]=\"displayHtml()\">\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;height:100%}.text-content{display:block;width:100%;height:100%;overflow:hidden;font-family:Arial,Helvetica,sans-serif;font-size:var(--text-size);line-height:1.25;color:var(--text-color);word-wrap:break-word}.text-content ::ng-deep p,.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3,.text-content ::ng-deep ul,.text-content ::ng-deep ol{margin:0}.text-content ::ng-deep h1{font-size:var(--text-h1-size)}.text-content ::ng-deep h2{font-size:var(--text-h2-size)}.text-content ::ng-deep h3{font-size:var(--text-h3-size)}.text-content ::ng-deep h1,.text-content ::ng-deep h2,.text-content ::ng-deep h3{font-weight:700;line-height:1.2}.text-content ::ng-deep ul,.text-content ::ng-deep ol{padding-left:1.4em}.text-content ::ng-deep p:empty:before{content:\"\\200b\"}.text-content ::ng-deep a{color:inherit}.text-content.editing{overflow:auto;cursor:text}\n"] }]
185
+ }], ctorParameters: () => [] });
186
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcG9uZW50LXRleHQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9yZXBvcnRzL2NvbXBvbmVudHMvY29tcG9uZW50LXRleHQvY29tcG9uZW50LXRleHQuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9yZXBvcnRzL2NvbXBvbmVudHMvY29tcG9uZW50LXRleHQvY29tcG9uZW50LXRleHQuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLHVCQUF1QixFQUFFLFNBQVMsRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQ25HLFNBQVMsRUFBRSxTQUFTLEdBQ3JCLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUM3QyxPQUFPLEVBQUUsWUFBWSxFQUFZLE1BQU0sMkJBQTJCLENBQUM7QUFFbkUsT0FBTyxFQUFFLHFCQUFxQixFQUFzQixNQUFNLHlCQUF5QixDQUFDO0FBRXBGLE9BQU8sRUFDTCxrQkFBa0IsRUFBRSxpQkFBaUIsRUFBRSxrQkFBa0IsRUFBb0IsY0FBYyxHQUM1RixNQUFNLFlBQVksQ0FBQzs7O0FBR3BCLHNFQUFzRTtBQUN0RSxNQUFNLFVBQVUsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztBQUV4SCw4RUFBOEU7QUFDOUUsTUFBTSxNQUFNLEdBQUc7SUFDYixpQkFBaUIs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Vzt3QkFDWCxxQkFBcUI7cUJBQ3RCLFFBQ0s7d0JBQ0osV0FBVyxFQUFFLG1CQUFtQjt3QkFDaEMsMkVBQTJFO3dCQUMzRSxrRUFBa0U7d0JBQ2xFLHFCQUFxQixFQUFFLGdCQUFnQjt3QkFDdkMsd0JBQXdCLEVBQUUsY0FBYzt3QkFDeEMsd0JBQXdCLEVBQUUsY0FBYzt3QkFDeEMsd0JBQXdCLEVBQUUsY0FBYzt3QkFDeEMsc0JBQXNCLEVBQUUsaUJBQWlCO3FCQUMxQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENoYW5nZURldGVjdGlvblN0cmF0ZWd5LCBDb21wb25lbnQsIERlc3Ryb3lSZWYsIEVsZW1lbnRSZWYsIGNvbXB1dGVkLCBlZmZlY3QsIGluamVjdCwgaW5wdXQsIG91dHB1dCxcbiAgdW50cmFja2VkLCB2aWV3Q2hpbGQsXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgRm9ybXNNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XG5pbXBvcnQgeyBEb21TYW5pdGl6ZXIsIFNhZmVIdG1sIH0gZnJvbSAnQGFuZ3VsYXIvcGxhdGZvcm0tYnJvd3Nlcic7XG5cbmltcG9ydCB7IEZzSHRtbEVkaXRvckNvbXBvbmVudCwgRnNIdG1sRWRpdG9yQ29uZmlnIH0gZnJvbSAnQGZpcmVzdGl0Y2gvaHRtbC1lZGl0b3InO1xuXG5pbXBvcnQge1xuICBERUZBVUxUX1RFWFRfQ09MT1IsIERFRkFVTFRfVEVYVF9TSVpFLCBIRUFESU5HX1RFWFRfU0laRVMsIFRleHRUb2tlbkNvbnRleHQsIGZpbGxUZXh0VG9rZW5zLFxufSBmcm9tICcuLi8uLi90ZXh0JztcblxuXG4vLyBQb2ludCBzaXplcyDigJQgdGhlIHJlcG9ydCdzIHR5cGUgdW5pdCAoMToxIHdpdGggUG93ZXJQb2ludCBhbmQgUERGKS5cbmNvbnN0IEZPTlRfU0laRVMgPSBbJzgnLCAnOScsICcxMCcsICcxMScsICcxMicsICcxNCcsICcxNicsICcxOCcsICcyMCcsICcyNCcsICcyOCcsICczMicsICczNicsICc0MCcsICc0OCcsICc2MCcsICc3MiddO1xuXG4vLyBUaGUgZmllbGRzIGEgYmxvY2sgY2FuIGNhcnJ5LCBmaWxsZWQgcGVyIHBhZ2Ugb24gdGhlIGNhbnZhcyBhbmQgaW4gZXhwb3J0cy5cbmNvbnN0IFRPS0VOUyA9IHtcbiAgJ3t7cGFnZS5udW1iZXJ9fSc6ICdQYWdlIG51bWJlcicsXG4gICd7e3BhZ2UubmFtZX19JzogJ1BhZ2UgbmFtZScsXG4gICd7e3JlcG9ydC5uYW1lfX0nOiAnUmVwb3J0IG5hbWUnLFxufTtcblxuLy8gV2hhdCB0aGUgZWRpdG9yIG1heSBwcm9kdWNlIOKAlCBleGFjdGx5IHdoYXQgdGhlIHNlcnZlciBrZWVwc1xuLy8gKFJlcG9ydFRleHRIdG1sKSBhbmQgdGhlIGV4cG9ydHMgZHJhdy4gQW55dGhpbmcgZWxzZSB3b3VsZCBlZGl0IGZpbmUgYW5kXG4vLyB0aGVuIHZhbmlzaCBvbiBzYXZlLlxuY29uc3QgQUxMT1dFRF9UQUdTID0gWydwJywgJ2gxJywgJ2gyJywgJ2gzJywgJ2JyJywgJ3N0cm9uZycsICdlbScsICd1JywgJ3MnLCAnc3BhbicsICdhJywgJ3VsJywgJ29sJywgJ2xpJ107XG5jb25zdCBBTExPV0VEX1NUWUxFUyA9IFsnY29sb3InLCAnYmFja2dyb3VuZC1jb2xvcicsICdmb250LXNpemUnLCAnZm9udC13ZWlnaHQnLCAnZm9udC1zdHlsZScsICd0ZXh0LWRlY29yYXRpb24nLCAnZm9udC1mYW1pbHknLCAndGV4dC1hbGlnbiddO1xuXG4vLyBGcm9hbGEncyBvd24gdG9vbGJhcnMgYW5kIHBvcHVwcyBsaXZlIG9uIHRoZSBib2R5LCBub3QgaW4gdGhlIGJsb2NrOyBhIHByZXNzXG4vLyBvbiBhbnkgb2YgdGhlbSBpcyBzdGlsbCBwYXJ0IG9mIGVkaXRpbmcgdGhlIGJsb2NrLlxuY29uc3QgRURJVE9SX0NIUk9NRSA9ICcuZnItdG9vbGJhciwgLmZyLXBvcHVwLCAuZnItZHJvcGRvd24tbWVudSwgLmZyLW1vZGFsLCAuZnItdG9vbHRpcCc7XG5cbi8vIEEgdGV4dCBibG9jazogYXV0aG9yZWQgcmljaCB0ZXh0IG9uIHRoZSBwYWdlLlxuLy9cbi8vIFJlYWQgbW9kZSByZW5kZXJzIHRoZSBzdG9yZWQgaHRtbCB3aXRoIGl0cyB0b2tlbnMgKHt7cGFnZS5udW1iZXJ9fSwg4oCmKSBmaWxsZWRcbi8vIGZvciB0aGUgcGFnZSBpdCBpcyBkcmF3biBvbi4gVGhlIGh0bWwgaGFzIGFscmVhZHkgYmVlbiByZWJ1aWx0IHNlcnZlci1zaWRlXG4vLyBmcm9tIGFuIGFsbG93bGlzdCAoUmVwb3J0VGV4dEh0bWwpLCB3aGljaCBpcyB3aGF0IG1ha2VzIGJpbmRpbmcgaXQgYXMgdHJ1c3RlZFxuLy8gbWFya3VwIHNhZmUg4oCUIEFuZ3VsYXIncyBvd24gc2FuaXRpemVyIHdvdWxkIHN0cmlwIHRoZSBpbmxpbmUgc3R5bGVzIHRoYXQgQVJFXG4vLyB0aGUgZm9ybWF0dGluZy5cbi8vXG4vLyBFZGl0IG1vZGUgbW91bnRzIDxmcy1odG1sLWVkaXRvcj4gaW4gaXRzIGZsb2F0aW5nIG1vZGUgaW4gcGxhY2Ug4oCUIHRoZSB3b3Jkc1xuLy8gc3RheSB3aGVyZSB0aGV5IHNpdCBvbiB0aGUgc2xpZGUg4oCUIHdpdGggRnJvYWxhJ3MgaW5saW5lIHRvb2xiYXIgcG9wcGluZyB1cFxuLy8gb3ZlciB0aGVtLiBUaGUgZWRpdG9yIGV4aXN0cyBvbmx5IHdoaWxlIGVkaXRpbmc7IHRoZSByYXcgaHRtbCBpcyBlZGl0ZWQsIHNvIGFcbi8vIHRva2VuIHJlYWRzIGFzIHt7cGFnZS5udW1iZXJ9fSB3aGlsZSBlZGl0aW5nIGFuZCBhcyB0aGUgbnVtYmVyIG90aGVyd2lzZS5cbi8vXG4vLyBUaGUgYmxvY2sgbmV2ZXIgc2F2ZXMgb24gaXRzIG93bjogaXQgZW1pdHMgdGhlIG5ldyBodG1sIHdoZW4gZWRpdGluZyBlbmRzXG4vLyBhbmQgdGhlIGhvc3QgcGVyc2lzdHMgaXQuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdhcHAtcmVwb3J0LWNvbXBvbmVudC10ZXh0JyxcbiAgdGVtcGxhdGVVcmw6ICcuL2NvbXBvbmVudC10ZXh0LmNvbXBvbmVudC5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vY29tcG9uZW50LXRleHQuY29tcG9uZW50LnNjc3MnXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtcbiAgICBGb3Jtc01vZHVsZSxcbiAgICBGc0h0bWxFZGl0b3JDb21wb25lbnQsXG4gIF0sXG4gIGhvc3Q6IHtcbiAgICAnKGtleWRvd24pJzogJ29uS2V5ZG93bigkZXZlbnQpJyxcbiAgICAvLyBUaGUgZG9jdW1lbnQgdHlwZSBzY2FsZSBjb21lcyBmcm9tIHRoZSBzYW1lIGNvbnN0YW50cyB0aGUgUG93ZXJQb2ludCBhbmRcbiAgICAvLyBQREYgZXhwb3J0cyBkcmF3IHdpdGgsIHNvIGEgcGFyYWdyYXBoIGlzIG9uZSBzaXplIGluIGFsbCB0aHJlZS5cbiAgICAnW3N0eWxlLi0tdGV4dC1zaXplXSc6ICd0eXBlU2NhbGUuYm9keScsXG4gICAgJ1tzdHlsZS4tLXRleHQtaDEtc2l6ZV0nOiAndHlwZVNjYWxlLmgxJyxcbiAgICAnW3N0eWxlLi0tdGV4dC1oMi1zaXplXSc6ICd0eXBlU2NhbGUuaDInLFxuICAgICdbc3R5bGUuLS10ZXh0LWgzLXNpemVdJzogJ3R5cGVTY2FsZS5oMycsXG4gICAgJ1tzdHlsZS4tLXRleHQtY29sb3JdJzogJ3R5cGVTY2FsZS5jb2xvcicsXG4gIH0sXG59KVxuZXhwb3J0IGNsYXNzIENvbXBvbmVudFRleHRDb21wb25lbnQge1xuXG4gIC8vIFRoZSBibG9jaydzIHN0b3JlZCBodG1sIChjb25maWcuaHRtbCkuIEl0cyBvd24gaW5wdXQgcmF0aGVyIHRoYW4gcmVhZCBvZmZcbiAgLy8gdGhlIGNvbXBvbmVudCwgc28gYSBzYXZlZCBlZGl0IOKAlCBhIG5ldyBzdHJpbmcgb24gdGhlIHNhbWUgY29tcG9uZW50IOKAlCBpc1xuICAvLyBzZWVuIGFzIGEgY2hhbmdlIGFuZCByZXBhaW50cy5cbiAgcHVibGljIHJlYWRvbmx5IGh0bWwgPSBpbnB1dCgnJyk7XG4gIHB1YmxpYyByZWFkb25seSB0b2tlbnMgPSBpbnB1dC5yZXF1aXJlZDxUZXh0VG9rZW5Db250ZXh0PigpO1xuICBwdWJsaWMgcmVhZG9ubHkgZWRpdGluZyA9IGlucHV0KGZhbHNlKTtcblxuICAvLyBUaGUgaHRtbCBhZnRlciBhbiBlZGl0LCBvbmx5IHdoZW4gaXQgY2hhbmdlZC5cbiAgcHVibGljIHJlYWRvbmx5IHNhdmVkID0gb3V0cHV0PHN0cmluZz4oKTtcblxuICAvLyBBIHByZXNzIGxhbmRlZCBvdXRzaWRlIHRoZSBibG9jayAob3IgRXNjYXBlKTogdGhlIGhvc3QgZW5kcyBlZGl0IG1vZGUsXG4gIC8vIHdoaWNoIHNhdmVzLlxuICBwdWJsaWMgcmVhZG9ubHkgZWRpdEVuZGVkID0gb3V0cHV0PHZvaWQ+KCk7XG5cbiAgcHVibGljIHJlYWRvbmx5IHR5cGVTY2FsZSA9IHtcbiAgICBib2R5OiBgJHtERUZBVUxUX1RFWFRfU0laRX1wdGAsXG4gICAgaDE6IGAke0hFQURJTkdfVEVYVF9TSVpFU1sxXX1wdGAsXG4gICAgaDI6IGAke0hFQURJTkdfVEVYVF9TSVpFU1syXX1wdGAsXG4gICAgaDM6IGAke0hFQURJTkdfVEVYVF9TSVpFU1szXX1wdGAsXG4gICAgY29sb3I6IERFRkFVTFRfVEVYVF9DT0xPUixcbiAgfTtcblxuICBwdWJsaWMgcmVhZG9ubHkgZWRpdG9yQ29uZmlnOiBGc0h0bWxFZGl0b3JDb25maWcgPSB7XG4gICAgZmxvYXRpbmc6IHRydWUsXG4gICAgYXV0b2ZvY3VzOiB0cnVlLFxuICAgIHBsYWNlaG9sZGVyOiAnVHlwZSBoZXJlJyxcbiAgICBidXR0b25zOiBbXG4gICAgICB7XG4gICAgICAgIG5hbWU6ICdpbnNlcnRGaWVsZCcsXG4gICAgICAgIHRpdGxlOiAnSW5zZXJ0IGZpZWxkJyxcbiAgICAgICAgc3ZnS2V5OiAndGFncycsXG4gICAgICAgIG9wdGlvbnM6IFRPS0VOUyxcbiAgICAgICAgY2xpY2s6IChlZGl0b3IsIHZhbHVlKSA9PiBlZGl0b3IuaHRtbC5pbnNlcnQodmFsdWUpLFxuICAgICAgfSxcbiAgICBdLFxuICAgIGZyb2FsYUNvbmZpZzoge1xuICAgICAgdG9vbGJhckJ1dHRvbnM6IFtcbiAgICAgICAgJ3BhcmFncmFwaEZvcm1hdCcsICdmb250U2l6ZScsICd8JyxcbiAgICAgICAgJ2JvbGQnLCAnaXRhbGljJywgJ3VuZGVybGluZScsICdzdHJpa2VUaHJvdWdoJywgJ3RleHRDb2xvcicsICd8JyxcbiAgICAgICAgJ2FsaWduJywgJ2Zvcm1hdFVMJywgJ2Zvcm1hdE9MJywgJ3wnLFxuICAgICAgICAnaW5zZXJ0TGluaycsICdpbnNlcnRGaWVsZCcsICdjbGVhckZvcm1hdHRpbmcnLFxuICAgICAgXSxcbiAgICAgIHBhcmFncmFwaEZvcm1hdDogeyBOOiAnTm9ybWFsJywgSDE6ICdIZWFkaW5nIDEnLCBIMjogJ0hlYWRpbmcgMicsIEgzOiAnSGVhZGluZyAzJyB9LFxuICAgICAgcGFyYWdyYXBoRm9ybWF0U2VsZWN0aW9uOiB0cnVlLFxuICAgICAgZm9udFNpemU6IEZPTlRfU0laRVMsXG4gICAgICBmb250U2l6ZVVuaXQ6ICdwdCcsXG4gICAgICBmb250U2l6ZVNlbGVjdGlvbjogdHJ1ZSxcbiAgICAgIGh0bWxBbGxvd2VkVGFnczogQUxMT1dFRF9UQUdTLFxuICAgICAgaHRtbEFsbG93ZWRBdHRyczogWydzdHlsZScsICdocmVmJywgJ3RhcmdldCcsICdyZWwnXSxcbiAgICAgIGh0bWxBbGxvd2VkU3R5bGVQcm9wczogQUxMT1dFRF9TVFlMRVMsXG4gICAgICBodG1sUmVtb3ZlVGFnczogWydzY3JpcHQnLCAnc3R5bGUnXSxcbiAgICAgIC8vIElubGluZSBzdHlsZXMgcmF0aGVyIHRoYW4gRnJvYWxhJ3MgZnItKiBjbGFzc2VzLCB3aGljaCBuZWl0aGVyIHRoZVxuICAgICAgLy8gc2VydmVyIG5vciB0aGUgZXhwb3J0cyBrbm93LlxuICAgICAgdXNlQ2xhc3NlczogZmFsc2UsXG4gICAgICBwYXN0ZVBsYWluOiB0cnVlLFxuICAgICAgLy8gRXZlcnkga2V5c3Ryb2tlIHJlYWNoZXMgdGhlIG1vZGVsIHN0cmFpZ2h0IGF3YXksIHNvIGVuZGluZyB0aGUgZWRpdCBhblxuICAgICAgLy8gaW5zdGFudCBhZnRlciB0eXBpbmcgbmV2ZXIgbG9zZXMgdGhlIGxhc3Qgd29yZC5cbiAgICAgIHR5cGluZ1RpbWVyOiAwLFxuICAgIH0sXG4gIH07XG5cbiAgcHVibGljIHJlYWRvbmx5IGRpc3BsYXlIdG1sID0gY29tcHV0ZWQ8U2FmZUh0bWw+KCgpID0+IHRoaXMuX3Nhbml0aXplci5ieXBhc3NTZWN1cml0eVRydXN0SHRtbChcbiAgICBmaWxsVGV4dFRva2Vucyh0aGlzLmh0bWwoKSwgdGhpcy50b2tlbnMoKSksXG4gICkpO1xuXG4gIC8vIFdoYXQgdGhlIGVkaXRvciBob2xkcyByaWdodCBub3cuXG4gIHB1YmxpYyBkcmFmdCA9ICcnO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgX2VkaXRvciA9IHZpZXdDaGlsZChGc0h0bWxFZGl0b3JDb21wb25lbnQpO1xuICBwcml2YXRlIHJlYWRvbmx5IF9zYW5pdGl6ZXIgPSBpbmplY3QoRG9tU2FuaXRpemVyKTtcbiAgcHJpdmF0ZSByZWFkb25seSBfaG9zdDogRWxlbWVudFJlZjxIVE1MRWxlbWVudD4gPSBpbmplY3QoRWxlbWVudFJlZik7XG4gIHByaXZhdGUgcmVhZG9ubHkgX2Rlc3Ryb3lSZWYgPSBpbmplY3QoRGVzdHJveVJlZik7XG4gIHByaXZhdGUgX2lzT3BlbiA9IGZhbHNlO1xuXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIC8vIE9wZW5pbmcgc2VlZHMgdGhlIGRyYWZ0OyBjbG9zaW5nIOKAlCB3aG9ldmVyIGNsb3NlZCBpdDogYSBwcmVzcyBvdXRzaWRlLFxuICAgIC8vIEVzY2FwZSwgdGhlIGJsb2NrIGJlaW5nIGRlc2VsZWN0ZWQgb3IgZWRpdCBtb2RlIGVuZGluZyDigJQgc2F2ZXMgaXQuXG4gICAgZWZmZWN0KCgpID0+IHtcbiAgICAgIGNvbnN0IGVkaXRpbmcgPSB0aGlzLmVkaXRpbmcoKTtcblxuICAgICAgdW50cmFja2VkKCgpID0+IHtcbiAgICAgICAgaWYgKGVkaXRpbmcgJiYgIXRoaXMuX2lzT3Blbikge1xuICAgICAgICAgIHRoaXMuX2lzT3BlbiA9IHRydWU7XG4gICAgICAgICAgdGhpcy5kcmFmdCA9IHRoaXMuaHRtbCgpO1xuICAgICAgICB9IGVsc2UgaWYgKCFlZGl0aW5nICYmIHRoaXMuX2lzT3Blbikge1xuICAgICAgICAgIHRoaXMuX2ZpbmlzaCgpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICB9KTtcblxuICAgIC8vIEEgcHJlc3MgYW55d2hlcmUgdGhhdCBpcyBub3QgdGhlIGJsb2NrIG9yIHRoZSBlZGl0b3IncyBvd24gY2hyb21lIGVuZHNcbiAgICAvLyB0aGUgZWRpdC4gQ2FwdHVyZWQsIHNvIGl0IGlzIHNlZW4gYmVmb3JlIHRoZSBjYW52YXMgb3IgYSBzaWJsaW5nXG4gICAgLy8gY29tcG9uZW50IGFjdHMgb24gdGhlIHNhbWUgcHJlc3MuXG4gICAgY29uc3Qgb25QcmVzcyA9IChldmVudDogUG9pbnRlckV2ZW50KSA9PiB0aGlzLl9vbkRvY3VtZW50UHJlc3MoZXZlbnQpO1xuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3BvaW50ZXJkb3duJywgb25QcmVzcywgdHJ1ZSk7XG4gICAgdGhpcy5fZGVzdHJveVJlZi5vbkRlc3Ryb3koKCkgPT4gZG9jdW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcigncG9pbnRlcmRvd24nLCBvblByZXNzLCB0cnVlKSk7XG4gIH1cblxuICBwdWJsaWMgb25LZXlkb3duKGV2ZW50OiBLZXlib2FyZEV2ZW50KTogdm9pZCB7XG4gICAgaWYgKHRoaXMuZWRpdGluZygpICYmIGV2ZW50LmtleSA9PT0gJ0VzY2FwZScpIHtcbiAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICB0aGlzLl9jbG9zZSgpO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgX29uRG9jdW1lbnRQcmVzcyhldmVudDogUG9pbnRlckV2ZW50KTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmVkaXRpbmcoKSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IHRhcmdldCA9IGV2ZW50LnRhcmdldCBhcyBIVE1MRWxlbWVudCB8IG51bGw7XG4gICAgaWYgKHRhcmdldCAmJiAodGhpcy5faG9zdC5uYXRpdmVFbGVtZW50LmNvbnRhaW5zKHRhcmdldCkgfHwgdGFyZ2V0LmNsb3Nlc3QoRURJVE9SX0NIUk9NRSkpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdGhpcy5fY2xvc2UoKTtcbiAgfVxuXG4gIC8vIFNhdmUgTk9XLCB0aGVuIGFzayB0aGUgaG9zdCB0byBjbG9zZS4gU2F2aW5nIGhlcmUgcmF0aGVyIHRoYW4gd2FpdGluZyBmb3JcbiAgLy8gdGhlIGVkaXRpbmcgaW5wdXQgdG8gZmxpcCBtYXR0ZXJzOiB0aGUgc2FtZSBwcmVzcyBtYXkgYmUgb24gYSBwYWdlIHRhYixcbiAgLy8gYW5kIGJ5IHRoZSB0aW1lIHRoZSBpbnB1dCBjaGFuZ2VkIHRoaXMgYmxvY2sgY291bGQgYWxyZWFkeSBiZSBkZXN0cm95ZWQsXG4gIC8vIHdpdGggaXRzIGVkaXQgZ29uZS5cbiAgcHJpdmF0ZSBfY2xvc2UoKTogdm9pZCB7XG4gICAgdGhpcy5fc3luY0RyYWZ0KCk7XG4gICAgdGhpcy5fZmluaXNoKCk7XG4gICAgdGhpcy5lZGl0RW5kZWQuZW1pdCgpO1xuICB9XG5cbiAgLy8gUmVhZCB0aGUgZWRpdG9yIGRpcmVjdGx5IHJhdGhlciB0aGFuIHRydXN0aW5nIHRoZSBsYXN0IG1vZGVsIHVwZGF0ZS5cbiAgcHJpdmF0ZSBfc3luY0RyYWZ0KCk6IHZvaWQge1xuICAgIGNvbnN0IGh0bWwgPSB0aGlzLl9lZGl0b3IoKT8uZWRpdG9yPy5odG1sPy5nZXQoKTtcbiAgICBpZiAodHlwZW9mIGh0bWwgPT09ICdzdHJpbmcnKSB7XG4gICAgICB0aGlzLmRyYWZ0ID0gaHRtbDtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIF9maW5pc2goKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLl9pc09wZW4pIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLl9pc09wZW4gPSBmYWxzZTtcbiAgICBpZiAodGhpcy5kcmFmdCAhPT0gdGhpcy5odG1sKCkpIHtcbiAgICAgIHRoaXMuc2F2ZWQuZW1pdCh0aGlzLmRyYWZ0KTtcbiAgICB9XG4gIH1cbn1cbiIsIkBpZiAoZWRpdGluZygpKSB7XG4gIDwhLS0gRmxvYXRpbmcgZnMtaHRtbC1lZGl0b3I6IG5vIGZyYW1lLCBGcm9hbGEncyBpbmxpbmUgdG9vbGJhciBwb3BzIHVwIG92ZXJcbiAgICAgICB0aGUgdGV4dC4gcG9pbnRlcmRvd24gc3RheXMgaGVyZSBzbyBhIHByZXNzIGluIHRoZSB0ZXh0IG5ldmVyIHN0YXJ0cyBhXG4gICAgICAgZHJhZyBvZiB0aGUgYmxvY2sgb3IgYSBwYW4gb2YgdGhlIGNhbnZhcy4gLS0+XG4gIDxmcy1odG1sLWVkaXRvclxuICAgICAgY2xhc3M9XCJ0ZXh0LWNvbnRlbnQgZWRpdGluZ1wiXG4gICAgICBbY29uZmlnXT1cImVkaXRvckNvbmZpZ1wiXG4gICAgICBbKG5nTW9kZWwpXT1cImRyYWZ0XCJcbiAgICAgIChwb2ludGVyZG93bik9XCIkZXZlbnQuc3RvcFByb3BhZ2F0aW9uKClcIlxuICAgICAgKG1vdXNlZG93bik9XCIkZXZlbnQuc3RvcFByb3BhZ2F0aW9uKClcIj5cbiAgPC9mcy1odG1sLWVkaXRvcj5cbn0gQGVsc2Uge1xuICA8ZGl2XG4gICAgICBjbGFzcz1cInRleHQtY29udGVudFwiXG4gICAgICBbaW5uZXJIVE1MXT1cImRpc3BsYXlIdG1sKClcIj5cbiAgPC9kaXY+XG59XG4iXX0=