@quicdata/analytics 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/auth.d.ts +19 -0
- package/core/auth.d.ts.map +1 -0
- package/core/auth.js +34 -0
- package/core/fetch-data.d.ts +115 -0
- package/core/fetch-data.d.ts.map +1 -0
- package/core/fetch-data.js +210 -0
- package/core/index.d.ts +9 -0
- package/core/index.d.ts.map +1 -0
- package/core/index.js +4 -0
- package/core/types.d.ts +250 -0
- package/core/types.d.ts.map +1 -0
- package/core/viewport-observer.d.ts +26 -0
- package/core/viewport-observer.d.ts.map +1 -0
- package/core/viewport-observer.js +38 -0
- package/core/widget-transform-runner.d.ts +22 -0
- package/core/widget-transform-runner.d.ts.map +1 -0
- package/core/widget-transform-runner.js +102 -0
- package/dashboard/dashboard-container.d.ts +100 -0
- package/dashboard/dashboard-container.d.ts.map +1 -0
- package/dashboard/dashboard-container.js +315 -0
- package/dashboard/index.d.ts +4 -0
- package/dashboard/index.d.ts.map +1 -0
- package/dashboard/index.js +2 -0
- package/designer/analytics-designer.d.ts +40 -0
- package/designer/analytics-designer.d.ts.map +1 -0
- package/designer/analytics-designer.js +267 -0
- package/designer/index.d.ts +5 -0
- package/designer/index.d.ts.map +1 -0
- package/designer/index.js +3 -0
- package/filters/filter-bar.d.ts +34 -0
- package/filters/filter-bar.d.ts.map +1 -0
- package/filters/filter-bar.js +233 -0
- package/filters/filter-button.d.ts +22 -0
- package/filters/filter-button.d.ts.map +1 -0
- package/filters/filter-button.js +86 -0
- package/filters/index.d.ts +7 -0
- package/filters/index.d.ts.map +1 -0
- package/filters/index.js +6 -0
- package/filters/widget-toolbar.d.ts +24 -0
- package/filters/widget-toolbar.d.ts.map +1 -0
- package/filters/widget-toolbar.js +216 -0
- package/index.d.ts +10 -0
- package/index.d.ts.map +1 -0
- package/index.js +6 -0
- package/package.json +27 -26
- package/widgets/analytics-report.d.ts +49 -0
- package/widgets/analytics-report.d.ts.map +1 -0
- package/widgets/analytics-report.js +306 -0
- package/widgets/analytics-widget.d.ts +39 -0
- package/widgets/analytics-widget.d.ts.map +1 -0
- package/widgets/analytics-widget.js +230 -0
- package/widgets/bar-chart.d.ts +13 -0
- package/widgets/bar-chart.d.ts.map +1 -0
- package/widgets/bar-chart.js +77 -0
- package/widgets/base-chart.d.ts +94 -0
- package/widgets/base-chart.d.ts.map +1 -0
- package/widgets/base-chart.js +535 -0
- package/widgets/index.d.ts +14 -0
- package/widgets/index.d.ts.map +1 -0
- package/widgets/index.js +14 -0
- package/widgets/line-chart.d.ts +13 -0
- package/widgets/line-chart.d.ts.map +1 -0
- package/widgets/line-chart.js +71 -0
- package/widgets/pie-chart.d.ts +13 -0
- package/widgets/pie-chart.d.ts.map +1 -0
- package/widgets/pie-chart.js +55 -0
- package/widgets/table.d.ts +101 -0
- package/widgets/table.d.ts.map +1 -0
- package/widgets/table.js +740 -0
- package/workers/widget-transform.worker.d.ts +21 -0
- package/workers/widget-transform.worker.d.ts.map +1 -0
- package/workers/widget-transform.worker.js +30 -0
- package/src/core/index.d.ts +0 -2
- package/src/core/index.d.ts.map +0 -1
- package/src/core/index.js +0 -3
- package/src/index.d.ts +0 -2
- package/src/index.d.ts.map +0 -1
- package/src/widgets/index.d.ts +0 -2
- package/src/widgets/index.d.ts.map +0 -1
- package/src/widgets/index.js +0 -3
- /package/{src/index.js → core/types.js} +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import type { EChartsOption } from 'echarts/types/dist/option';
|
|
3
|
+
import type { WidgetDefinitionSubset } from '../core/fetch-data.js';
|
|
4
|
+
import type { DashboardContainer } from '../dashboard/dashboard-container.js';
|
|
5
|
+
import '../filters/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Base Lit + ECharts widget. Fetches data from data-url (Laravel analytics API)
|
|
8
|
+
* or uses inline data, then renders Apache ECharts.
|
|
9
|
+
* When dashboard is set, merges dashboard params with dataParams and listens for
|
|
10
|
+
* analytics-dashboard-filter-change and analytics-dashboard-refresh.
|
|
11
|
+
*/
|
|
12
|
+
export declare abstract class BaseChartWidget extends LitElement {
|
|
13
|
+
static styles: import("lit").CSSResult;
|
|
14
|
+
/** Optional title override. When unset, the widget uses the title from the API definition. */
|
|
15
|
+
title: string;
|
|
16
|
+
/** API URL to fetch data (e.g. /api/analytics/widgets/1/data). When widgetId + apiUrl are set, this is overridden by buildWidgetDataUrl(apiUrl, widgetId). */
|
|
17
|
+
dataUrl: string;
|
|
18
|
+
/** Widget id (used with apiUrl to build data URL: /widgets/{id}/data). */
|
|
19
|
+
widgetId: string;
|
|
20
|
+
/** API base URL (host + prefix, e.g. /api/analytics). Used with widgetId to build data URL. */
|
|
21
|
+
apiUrl: string;
|
|
22
|
+
/** Query params for data-url (e.g. date_from, date_to). Widget params override dashboard params for same key. */
|
|
23
|
+
dataParams: Record<string, string | number | boolean>;
|
|
24
|
+
/** When set, widget receives dashboard filter/refresh events and merges dashboard params into requests. */
|
|
25
|
+
dashboard: DashboardContainer | null;
|
|
26
|
+
/** Inline data instead of fetching (for designer preview). */
|
|
27
|
+
data: Record<string, unknown>[] | null;
|
|
28
|
+
/** Inline meta (columns) when using inline data. */
|
|
29
|
+
meta: {
|
|
30
|
+
columns: Record<string, {
|
|
31
|
+
type?: string;
|
|
32
|
+
}>;
|
|
33
|
+
};
|
|
34
|
+
/** ECharts option overrides merged on top of buildOption() result. */
|
|
35
|
+
options: Partial<EChartsOption>;
|
|
36
|
+
/** When true, only fetch/render when widget enters viewport; pause refresh when out of viewport. */
|
|
37
|
+
lazy: boolean;
|
|
38
|
+
/** CSS margin around viewport to trigger visibility earlier (e.g. '200px'). Used when lazy is true. */
|
|
39
|
+
prefetchMargin: string;
|
|
40
|
+
/** When set (e.g. by analytics-widget), definition is not fetched again; used to avoid duplicate definition requests. */
|
|
41
|
+
initialDefinition: WidgetDefinitionSubset | undefined;
|
|
42
|
+
private _loading;
|
|
43
|
+
private _error;
|
|
44
|
+
private _loadGeneration;
|
|
45
|
+
private _loadDataScheduled;
|
|
46
|
+
private _ignoreNextFilterChange;
|
|
47
|
+
private _dashboardParams;
|
|
48
|
+
private _widgetFilters;
|
|
49
|
+
private _widgetFilterParams;
|
|
50
|
+
private _hideFilter;
|
|
51
|
+
private _widgetData;
|
|
52
|
+
private _chart;
|
|
53
|
+
private _resizeObserver;
|
|
54
|
+
private _resizeDelayId;
|
|
55
|
+
private _resizeDebounceId;
|
|
56
|
+
private _dashboardEl;
|
|
57
|
+
/** Initial delay (ms) before entering debounce; each new resize signal cancels and restarts. */
|
|
58
|
+
private static readonly _RESIZE_DELAY_MS;
|
|
59
|
+
/** Debounce (ms) after delay; resize runs only when no signal for this long. */
|
|
60
|
+
private static readonly _RESIZE_DEBOUNCE_MS;
|
|
61
|
+
private _viewportVisible;
|
|
62
|
+
/** Title from widget definition (API). Optional `title` attribute overrides this. */
|
|
63
|
+
private _definitionTitle;
|
|
64
|
+
private _hasLoadedOnce;
|
|
65
|
+
private _viewportUnobserve;
|
|
66
|
+
constructor();
|
|
67
|
+
private _boundOnDashboardFilterChange;
|
|
68
|
+
private _boundOnDashboardRefresh;
|
|
69
|
+
connectedCallback(): void;
|
|
70
|
+
disconnectedCallback(): void;
|
|
71
|
+
updated(changed: Map<string, unknown>): void;
|
|
72
|
+
private _attachDashboard;
|
|
73
|
+
private _detachDashboard;
|
|
74
|
+
firstUpdated(): void;
|
|
75
|
+
private _getEffectiveDataUrl;
|
|
76
|
+
/** Display title: optional host `title` overrides definition title from API. */
|
|
77
|
+
private get _effectiveTitle();
|
|
78
|
+
/** Run a callback after the current update cycle to avoid Lit "change-in-update" warning. */
|
|
79
|
+
private _defer;
|
|
80
|
+
private _getEffectiveParams;
|
|
81
|
+
private _loadData;
|
|
82
|
+
private _renderChart;
|
|
83
|
+
/**
|
|
84
|
+
* Build ECharts option from API data. Override in subclasses (bar, line, pie).
|
|
85
|
+
* When widgetData has categories + series (e.g. from split_by), use those for bar/line.
|
|
86
|
+
* Default: generic grid + empty series.
|
|
87
|
+
*/
|
|
88
|
+
buildOption(_data: Record<string, unknown>[], _meta: Record<string, {
|
|
89
|
+
type?: string;
|
|
90
|
+
}>, _widgetData?: unknown): EChartsOption;
|
|
91
|
+
private _onWidgetFilterChange;
|
|
92
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=base-chart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-chart.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/base-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,KAAK,EAA0B,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAO5F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAM9E,OAAO,qBAAqB,CAAC;AAE7B;;;;;GAKG;AACH,8BAAsB,eAAgB,SAAQ,UAAU;IACtD,OAAgB,MAAM,0BAuFpB;IAEF,8FAA8F;IAC1D,KAAK,EAAE,MAAM,CAAC;IAElD,8JAA8J;IACnG,OAAO,EAAE,MAAM,CAAC;IAE3E,0EAA0E;IACd,QAAQ,EAAE,MAAM,CAAC;IAE7E,+FAA+F;IACrC,MAAM,EAAE,MAAM,CAAC;IAEzE,iHAAiH;IAC7E,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAE1F,2GAA2G;IACvE,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAEzE,8DAA8D;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC;IAE3E,oDAAoD;IAChB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAEzF,sEAAsE;IAClC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAEpE,oGAAoG;IAC/D,IAAI,EAAE,OAAO,CAAC;IAEnD,uGAAuG;IACrC,cAAc,EAAE,MAAM,CAAC;IAEzF,yHAAyH;IACrF,iBAAiB,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAE1F,QAAyB,QAAQ,CAAU;IAC3C,QAAyB,MAAM,CAAgB;IAC/C,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,uBAAuB,CAAS;IACxC,QAAyB,gBAAgB,CAA4C;IACrF,QAAyB,cAAc,CAAqB;IAC5D,QAAyB,mBAAmB,CAA4C;IACxF,QAAyB,WAAW,CAAU;IAC9C,QAAyB,WAAW,CAAU;IAC9C,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,iBAAiB,CAA8C;IACvE,OAAO,CAAC,YAAY,CAAmC;IAEvD,gGAAgG;IAChG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAO;IAC/C,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAO;IAClD,QAAyB,gBAAgB,CAAU;IACnD,qFAAqF;IACrF,QAAyB,gBAAgB,CAAS;IAClD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,kBAAkB,CAA6B;;IA0BvD,OAAO,CAAC,6BAA6B,CAInC;IACF,OAAO,CAAC,wBAAwB,CAG9B;IAEO,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAc5B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IA0CrD,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,gBAAgB;IASf,YAAY,IAAI,IAAI;IAqC7B,OAAO,CAAC,oBAAoB;IAO5B,gFAAgF;IAChF,OAAO,KAAK,eAAe,GAE1B;IAED,6FAA6F;IAC7F,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,mBAAmB;YA2Bb,SAAS;IAuFvB,OAAO,CAAC,YAAY;IAuBpB;;;;OAIG;IACH,WAAW,CACT,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EACxC,WAAW,CAAC,EAAE,OAAO,GACpB,aAAa;IAShB,OAAO,CAAC,qBAAqB;IAIpB,MAAM;CAiChB"}
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
3
|
+
import { property, state } from 'lit/decorators.js';
|
|
4
|
+
import * as echarts from 'echarts';
|
|
5
|
+
import { buildWidgetDataUrl, fetchAnalyticsData, fetchWidgetData, fetchWidgetDefinition, } from '../core/fetch-data.js';
|
|
6
|
+
import { EVENT_DASHBOARD_FILTER_CHANGE, EVENT_DASHBOARD_REFRESH, } from '../dashboard/index.js';
|
|
7
|
+
import { observeViewport } from '../core/viewport-observer.js';
|
|
8
|
+
import '../filters/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Base Lit + ECharts widget. Fetches data from data-url (Laravel analytics API)
|
|
11
|
+
* or uses inline data, then renders Apache ECharts.
|
|
12
|
+
* When dashboard is set, merges dashboard params with dataParams and listens for
|
|
13
|
+
* analytics-dashboard-filter-change and analytics-dashboard-refresh.
|
|
14
|
+
*/
|
|
15
|
+
export class BaseChartWidget extends LitElement {
|
|
16
|
+
static { this.styles = css `
|
|
17
|
+
:host {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
min-height: 120px;
|
|
23
|
+
}
|
|
24
|
+
.chart {
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
min-height: 120px;
|
|
28
|
+
}
|
|
29
|
+
.error {
|
|
30
|
+
color: #c00;
|
|
31
|
+
padding: 0.5rem;
|
|
32
|
+
font-size: 0.875rem;
|
|
33
|
+
}
|
|
34
|
+
.loading-body {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
min-height: 100px;
|
|
39
|
+
padding: 1rem;
|
|
40
|
+
}
|
|
41
|
+
.loading-overlay {
|
|
42
|
+
position: absolute;
|
|
43
|
+
inset: 0;
|
|
44
|
+
z-index: 2;
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
background: rgba(255, 255, 255, 0.65);
|
|
49
|
+
backdrop-filter: blur(4px);
|
|
50
|
+
-webkit-backdrop-filter: blur(4px);
|
|
51
|
+
border-radius: 4px;
|
|
52
|
+
}
|
|
53
|
+
.loading-spinner {
|
|
54
|
+
width: 2rem;
|
|
55
|
+
height: 2rem;
|
|
56
|
+
border: 2px solid #e5e7eb;
|
|
57
|
+
border-top-color: #3b82f6;
|
|
58
|
+
border-radius: 50%;
|
|
59
|
+
animation: analytics-spin 0.7s linear infinite;
|
|
60
|
+
}
|
|
61
|
+
@keyframes analytics-spin {
|
|
62
|
+
to { transform: rotate(360deg); }
|
|
63
|
+
}
|
|
64
|
+
.widget-with-toolbar {
|
|
65
|
+
position: relative;
|
|
66
|
+
width: 100%;
|
|
67
|
+
height: 100%;
|
|
68
|
+
min-height: 120px;
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
}
|
|
72
|
+
.widget-with-toolbar:hover analytics-widget-toolbar {
|
|
73
|
+
opacity: 1;
|
|
74
|
+
pointer-events: auto;
|
|
75
|
+
}
|
|
76
|
+
.widget-header {
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
padding: 0.5rem 0.75rem 0.25rem 0.75rem;
|
|
79
|
+
min-width: 0;
|
|
80
|
+
}
|
|
81
|
+
.widget-title {
|
|
82
|
+
margin: 0;
|
|
83
|
+
font-size: 0.9375rem;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
color: #374151;
|
|
86
|
+
line-height: 1.3;
|
|
87
|
+
display: -webkit-box;
|
|
88
|
+
-webkit-box-orient: vertical;
|
|
89
|
+
-webkit-line-clamp: 1;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
text-overflow: ellipsis;
|
|
92
|
+
}
|
|
93
|
+
.widget-body {
|
|
94
|
+
flex: 1;
|
|
95
|
+
min-height: 0;
|
|
96
|
+
position: relative;
|
|
97
|
+
box-sizing: border-box;
|
|
98
|
+
}
|
|
99
|
+
.widget-body .chart {
|
|
100
|
+
height: 100%;
|
|
101
|
+
min-height: 100px;
|
|
102
|
+
}
|
|
103
|
+
`; }
|
|
104
|
+
/** Initial delay (ms) before entering debounce; each new resize signal cancels and restarts. */
|
|
105
|
+
static { this._RESIZE_DELAY_MS = 300; }
|
|
106
|
+
/** Debounce (ms) after delay; resize runs only when no signal for this long. */
|
|
107
|
+
static { this._RESIZE_DEBOUNCE_MS = 300; }
|
|
108
|
+
constructor() {
|
|
109
|
+
super();
|
|
110
|
+
this._loadGeneration = 0;
|
|
111
|
+
this._loadDataScheduled = false;
|
|
112
|
+
this._ignoreNextFilterChange = false;
|
|
113
|
+
this._chart = null;
|
|
114
|
+
this._resizeObserver = null;
|
|
115
|
+
this._resizeDelayId = null;
|
|
116
|
+
this._resizeDebounceId = null;
|
|
117
|
+
this._dashboardEl = null;
|
|
118
|
+
this._hasLoadedOnce = false;
|
|
119
|
+
this._viewportUnobserve = null;
|
|
120
|
+
this._boundOnDashboardFilterChange = (e) => {
|
|
121
|
+
const ce = e;
|
|
122
|
+
this._dashboardParams = { ...(ce.detail?.params ?? {}) };
|
|
123
|
+
this._loadData(); // refetch with new dashboard params
|
|
124
|
+
};
|
|
125
|
+
this._boundOnDashboardRefresh = () => {
|
|
126
|
+
if (this.lazy && !this._viewportVisible)
|
|
127
|
+
return;
|
|
128
|
+
this._loadData(); // no state change; refetch with current params
|
|
129
|
+
};
|
|
130
|
+
this.title = '';
|
|
131
|
+
this.dataUrl = '';
|
|
132
|
+
this.widgetId = '';
|
|
133
|
+
this.apiUrl = '';
|
|
134
|
+
this.dataParams = {};
|
|
135
|
+
this.dashboard = null;
|
|
136
|
+
this.data = null;
|
|
137
|
+
this.meta = { columns: {} };
|
|
138
|
+
this.options = {};
|
|
139
|
+
this.lazy = false;
|
|
140
|
+
this.prefetchMargin = '200px';
|
|
141
|
+
this.initialDefinition = undefined;
|
|
142
|
+
this._loading = false;
|
|
143
|
+
this._error = null;
|
|
144
|
+
this._dashboardParams = {};
|
|
145
|
+
this._widgetFilters = [];
|
|
146
|
+
this._widgetFilterParams = {};
|
|
147
|
+
this._hideFilter = false;
|
|
148
|
+
this._widgetData = null;
|
|
149
|
+
this._viewportVisible = true;
|
|
150
|
+
this._definitionTitle = '';
|
|
151
|
+
}
|
|
152
|
+
connectedCallback() {
|
|
153
|
+
super.connectedCallback();
|
|
154
|
+
this._attachDashboard();
|
|
155
|
+
}
|
|
156
|
+
disconnectedCallback() {
|
|
157
|
+
this._viewportUnobserve?.();
|
|
158
|
+
this._viewportUnobserve = null;
|
|
159
|
+
this._detachDashboard();
|
|
160
|
+
if (this._resizeDebounceId != null) {
|
|
161
|
+
clearTimeout(this._resizeDebounceId);
|
|
162
|
+
this._resizeDebounceId = null;
|
|
163
|
+
}
|
|
164
|
+
this._resizeObserver?.disconnect();
|
|
165
|
+
this._chart?.dispose();
|
|
166
|
+
this._chart = null;
|
|
167
|
+
super.disconnectedCallback();
|
|
168
|
+
}
|
|
169
|
+
updated(changed) {
|
|
170
|
+
if (changed.has('dashboard')) {
|
|
171
|
+
this._detachDashboard();
|
|
172
|
+
this._attachDashboard();
|
|
173
|
+
}
|
|
174
|
+
if (changed.has('widgetId') || changed.has('apiUrl')) {
|
|
175
|
+
this._widgetFilters = [];
|
|
176
|
+
this._widgetFilterParams = {};
|
|
177
|
+
this._definitionTitle = '';
|
|
178
|
+
this._ignoreNextFilterChange = true;
|
|
179
|
+
}
|
|
180
|
+
const onlyFilterState = (changed.has('_widgetFilterParams') || changed.has('_widgetFilters')) &&
|
|
181
|
+
!changed.has('dataUrl') &&
|
|
182
|
+
!changed.has('dataParams') &&
|
|
183
|
+
!changed.has('widgetId') &&
|
|
184
|
+
!changed.has('apiUrl');
|
|
185
|
+
if (onlyFilterState && this._ignoreNextFilterChange) {
|
|
186
|
+
this._ignoreNextFilterChange = false;
|
|
187
|
+
}
|
|
188
|
+
else if (changed.has('dataUrl') ||
|
|
189
|
+
changed.has('dataParams') ||
|
|
190
|
+
changed.has('widgetId') ||
|
|
191
|
+
changed.has('apiUrl') ||
|
|
192
|
+
changed.has('_widgetFilterParams')) {
|
|
193
|
+
if (!this._loadDataScheduled) {
|
|
194
|
+
this._loadDataScheduled = true;
|
|
195
|
+
queueMicrotask(() => {
|
|
196
|
+
this._loadDataScheduled = false;
|
|
197
|
+
this._loadData();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (changed.has('lazy')) {
|
|
202
|
+
this._viewportVisible = !this.lazy;
|
|
203
|
+
}
|
|
204
|
+
if (changed.has('data') || changed.has('meta') || changed.has('options') || this._chart) {
|
|
205
|
+
this._renderChart();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
_attachDashboard() {
|
|
209
|
+
const d = this.dashboard;
|
|
210
|
+
if (!d)
|
|
211
|
+
return;
|
|
212
|
+
this._dashboardEl = d;
|
|
213
|
+
this._dashboardParams = typeof d.getDashboardParams === 'function' ? d.getDashboardParams() : {};
|
|
214
|
+
d.addEventListener(EVENT_DASHBOARD_FILTER_CHANGE, this._boundOnDashboardFilterChange);
|
|
215
|
+
d.addEventListener(EVENT_DASHBOARD_REFRESH, this._boundOnDashboardRefresh);
|
|
216
|
+
}
|
|
217
|
+
_detachDashboard() {
|
|
218
|
+
const d = this._dashboardEl;
|
|
219
|
+
if (!d)
|
|
220
|
+
return;
|
|
221
|
+
d.removeEventListener(EVENT_DASHBOARD_FILTER_CHANGE, this._boundOnDashboardFilterChange);
|
|
222
|
+
d.removeEventListener(EVENT_DASHBOARD_REFRESH, this._boundOnDashboardRefresh);
|
|
223
|
+
this._dashboardEl = null;
|
|
224
|
+
this._dashboardParams = {};
|
|
225
|
+
}
|
|
226
|
+
firstUpdated() {
|
|
227
|
+
const container = this.renderRoot.querySelector('.chart');
|
|
228
|
+
if (container) {
|
|
229
|
+
this._resizeObserver = new ResizeObserver(() => {
|
|
230
|
+
if (this._resizeDelayId != null)
|
|
231
|
+
clearTimeout(this._resizeDelayId);
|
|
232
|
+
if (this._resizeDebounceId != null)
|
|
233
|
+
clearTimeout(this._resizeDebounceId);
|
|
234
|
+
this._resizeDelayId = setTimeout(() => {
|
|
235
|
+
this._resizeDelayId = null;
|
|
236
|
+
this._resizeDebounceId = setTimeout(() => {
|
|
237
|
+
this._resizeDebounceId = null;
|
|
238
|
+
this._chart?.resize();
|
|
239
|
+
}, BaseChartWidget._RESIZE_DEBOUNCE_MS);
|
|
240
|
+
}, BaseChartWidget._RESIZE_DELAY_MS);
|
|
241
|
+
});
|
|
242
|
+
this._resizeObserver.observe(container);
|
|
243
|
+
}
|
|
244
|
+
if (this.lazy) {
|
|
245
|
+
this._viewportVisible = false;
|
|
246
|
+
this._viewportUnobserve = observeViewport(this, { rootMargin: this.prefetchMargin || '200px', threshold: 0 }, {
|
|
247
|
+
onEnter: () => {
|
|
248
|
+
this._viewportVisible = true;
|
|
249
|
+
if (!this._hasLoadedOnce) {
|
|
250
|
+
this._hasLoadedOnce = true;
|
|
251
|
+
this._loadData();
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
onLeave: () => {
|
|
255
|
+
this._viewportVisible = false;
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
_getEffectiveDataUrl() {
|
|
261
|
+
if (this.apiUrl && this.widgetId) {
|
|
262
|
+
return buildWidgetDataUrl(this.apiUrl, this.widgetId);
|
|
263
|
+
}
|
|
264
|
+
return this.dataUrl;
|
|
265
|
+
}
|
|
266
|
+
/** Display title: optional host `title` overrides definition title from API. */
|
|
267
|
+
get _effectiveTitle() {
|
|
268
|
+
return (this.title != null && this.title !== '') ? this.title : (this._definitionTitle ?? '');
|
|
269
|
+
}
|
|
270
|
+
/** Run a callback after the current update cycle to avoid Lit "change-in-update" warning. */
|
|
271
|
+
_defer(fn) {
|
|
272
|
+
if (typeof requestAnimationFrame !== 'undefined') {
|
|
273
|
+
requestAnimationFrame(fn);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
setTimeout(fn, 0);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
_getEffectiveParams() {
|
|
280
|
+
const dashboardParams = this._dashboardParams;
|
|
281
|
+
const widgetParams = this.dataParams ?? {};
|
|
282
|
+
let widgetFilterParams = {};
|
|
283
|
+
if (this._hideFilter) {
|
|
284
|
+
for (const f of this._widgetFilters) {
|
|
285
|
+
if (f.default_value !== undefined && f.default_value !== null) {
|
|
286
|
+
widgetFilterParams[f.param_name] = f.default_value;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else if (this._widgetFilters.length > 0) {
|
|
291
|
+
for (const f of this._widgetFilters) {
|
|
292
|
+
const setByUser = f.param_name in this._widgetFilterParams;
|
|
293
|
+
widgetFilterParams[f.param_name] = setByUser
|
|
294
|
+
? (this._widgetFilterParams[f.param_name] ?? '')
|
|
295
|
+
: (f.default_value ?? '');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
widgetFilterParams = { ...this._widgetFilterParams };
|
|
300
|
+
}
|
|
301
|
+
const out = { ...widgetFilterParams, ...widgetParams };
|
|
302
|
+
if (Object.keys(dashboardParams).length > 0) {
|
|
303
|
+
out.dashboard_params = dashboardParams;
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
async _loadData() {
|
|
308
|
+
if (this.lazy && !this._viewportVisible)
|
|
309
|
+
return;
|
|
310
|
+
if (this.data != null && !this.widgetId) {
|
|
311
|
+
this._error = null;
|
|
312
|
+
this._loading = false;
|
|
313
|
+
this._renderChart();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const dataUrl = this._getEffectiveDataUrl();
|
|
317
|
+
if (!dataUrl) {
|
|
318
|
+
this._error = null;
|
|
319
|
+
this._loading = false;
|
|
320
|
+
this._renderChart();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const gen = ++this._loadGeneration;
|
|
324
|
+
this._loading = true;
|
|
325
|
+
this._error = null;
|
|
326
|
+
try {
|
|
327
|
+
const useWidgetApi = Boolean(this.apiUrl && this.widgetId);
|
|
328
|
+
if (useWidgetApi) {
|
|
329
|
+
let params;
|
|
330
|
+
if (this._widgetFilters.length === 0) {
|
|
331
|
+
const def = this.initialDefinition ?? (await fetchWidgetDefinition(this.apiUrl, this.widgetId));
|
|
332
|
+
this._definitionTitle = def.title ?? '';
|
|
333
|
+
this._widgetFilters = def.filters ?? [];
|
|
334
|
+
this._hideFilter = def.hide_filter ?? false;
|
|
335
|
+
const defaultParams = {};
|
|
336
|
+
for (const f of (def.filters ?? [])) {
|
|
337
|
+
if (f.default_value !== undefined && f.default_value !== null) {
|
|
338
|
+
defaultParams[f.param_name] = f.default_value;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
this._widgetFilterParams = { ...defaultParams, ...this._widgetFilterParams };
|
|
342
|
+
this._ignoreNextFilterChange = true;
|
|
343
|
+
params = { ...defaultParams, ...(this.dataParams ?? {}) };
|
|
344
|
+
if (Object.keys(this._dashboardParams).length > 0) {
|
|
345
|
+
params.dashboard_params = this._dashboardParams;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
params = this._getEffectiveParams();
|
|
350
|
+
}
|
|
351
|
+
const res = await fetchWidgetData(dataUrl, params);
|
|
352
|
+
if (gen !== this._loadGeneration)
|
|
353
|
+
return;
|
|
354
|
+
const data = res.data ?? [];
|
|
355
|
+
const meta = res.meta ?? { columns: {} };
|
|
356
|
+
const widgetData = res.widgetData ?? null;
|
|
357
|
+
const filters = res.filters;
|
|
358
|
+
const hideFilter = res.hide_filter;
|
|
359
|
+
this._defer(() => {
|
|
360
|
+
if (gen !== this._loadGeneration)
|
|
361
|
+
return;
|
|
362
|
+
this.data = data;
|
|
363
|
+
this.meta = meta;
|
|
364
|
+
this._widgetData = widgetData;
|
|
365
|
+
if (filters?.length)
|
|
366
|
+
this._widgetFilters = filters;
|
|
367
|
+
if (hideFilter !== undefined)
|
|
368
|
+
this._hideFilter = hideFilter;
|
|
369
|
+
this._loading = false;
|
|
370
|
+
this._renderChart();
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
const params = this._getEffectiveParams();
|
|
375
|
+
const res = await fetchAnalyticsData(dataUrl, params);
|
|
376
|
+
if (gen !== this._loadGeneration)
|
|
377
|
+
return;
|
|
378
|
+
const data = res.data ?? [];
|
|
379
|
+
const meta = res.meta ?? { columns: {} };
|
|
380
|
+
this._defer(() => {
|
|
381
|
+
if (gen !== this._loadGeneration)
|
|
382
|
+
return;
|
|
383
|
+
this.data = data;
|
|
384
|
+
this.meta = meta;
|
|
385
|
+
this._widgetData = null;
|
|
386
|
+
this._loading = false;
|
|
387
|
+
this._renderChart();
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (e) {
|
|
392
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
393
|
+
this._defer(() => {
|
|
394
|
+
if (gen !== this._loadGeneration)
|
|
395
|
+
return;
|
|
396
|
+
this._error = errMsg;
|
|
397
|
+
this.data = [];
|
|
398
|
+
this._widgetData = null;
|
|
399
|
+
this._loading = false;
|
|
400
|
+
this._renderChart();
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
_renderChart() {
|
|
405
|
+
const container = this.renderRoot.querySelector('.chart');
|
|
406
|
+
if (!container)
|
|
407
|
+
return;
|
|
408
|
+
const rows = this.data ?? [];
|
|
409
|
+
const meta = this.meta?.columns ?? {};
|
|
410
|
+
const baseOption = this.buildOption(rows, meta, this._widgetData ?? null);
|
|
411
|
+
const merged = {
|
|
412
|
+
...baseOption,
|
|
413
|
+
...this.options,
|
|
414
|
+
};
|
|
415
|
+
// Re-init if container changed (e.g. after loading/error view replaced DOM)
|
|
416
|
+
if (this._chart && this._chart.getDom() !== container) {
|
|
417
|
+
this._chart.dispose();
|
|
418
|
+
this._chart = null;
|
|
419
|
+
}
|
|
420
|
+
if (!this._chart) {
|
|
421
|
+
this._chart = echarts.init(container);
|
|
422
|
+
}
|
|
423
|
+
this._chart.setOption(merged, { notMerge: true });
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Build ECharts option from API data. Override in subclasses (bar, line, pie).
|
|
427
|
+
* When widgetData has categories + series (e.g. from split_by), use those for bar/line.
|
|
428
|
+
* Default: generic grid + empty series.
|
|
429
|
+
*/
|
|
430
|
+
buildOption(_data, _meta, _widgetData) {
|
|
431
|
+
return {
|
|
432
|
+
grid: { left: 48, right: 24, top: 24, bottom: 48 },
|
|
433
|
+
xAxis: { type: 'category', data: [] },
|
|
434
|
+
yAxis: { type: 'value' },
|
|
435
|
+
series: [],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
_onWidgetFilterChange(e) {
|
|
439
|
+
this._widgetFilterParams = { ...(e.detail?.params ?? {}) };
|
|
440
|
+
}
|
|
441
|
+
render() {
|
|
442
|
+
const hasWidgetFilters = this._widgetFilters.length > 0 && !this._hideFilter;
|
|
443
|
+
const widgetActiveCount = Object.keys(this._widgetFilterParams).filter((k) => this._widgetFilterParams[k] !== '' && this._widgetFilterParams[k] != null).length;
|
|
444
|
+
return html `
|
|
445
|
+
<div class="widget-with-toolbar">
|
|
446
|
+
${this._effectiveTitle
|
|
447
|
+
? html `<header class="widget-header"><h2 class="widget-title">${this._effectiveTitle}</h2></header>`
|
|
448
|
+
: ''}
|
|
449
|
+
<div class="widget-body">
|
|
450
|
+
${this._error
|
|
451
|
+
? html `<div class="error">${this._error}</div>`
|
|
452
|
+
: html `<div class="chart"></div>`}
|
|
453
|
+
${this._loading
|
|
454
|
+
? html `<div class="loading-overlay" aria-busy="true" aria-live="polite">
|
|
455
|
+
<div class="loading-spinner" aria-hidden="true"></div>
|
|
456
|
+
</div>`
|
|
457
|
+
: nothing}
|
|
458
|
+
</div>
|
|
459
|
+
${hasWidgetFilters
|
|
460
|
+
? html `
|
|
461
|
+
<analytics-widget-toolbar
|
|
462
|
+
.filters=${this._widgetFilters}
|
|
463
|
+
.values=${this._widgetFilterParams}
|
|
464
|
+
.activeCount=${widgetActiveCount}
|
|
465
|
+
@analytics-filter-change=${this._onWidgetFilterChange}
|
|
466
|
+
></analytics-widget-toolbar>
|
|
467
|
+
`
|
|
468
|
+
: ''}
|
|
469
|
+
</div>
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
__decorate([
|
|
474
|
+
property({ type: String })
|
|
475
|
+
], BaseChartWidget.prototype, "title", void 0);
|
|
476
|
+
__decorate([
|
|
477
|
+
property({ type: String, attribute: 'data-url' })
|
|
478
|
+
], BaseChartWidget.prototype, "dataUrl", void 0);
|
|
479
|
+
__decorate([
|
|
480
|
+
property({ type: String, attribute: 'widget-id' })
|
|
481
|
+
], BaseChartWidget.prototype, "widgetId", void 0);
|
|
482
|
+
__decorate([
|
|
483
|
+
property({ type: String, attribute: 'api-url' })
|
|
484
|
+
], BaseChartWidget.prototype, "apiUrl", void 0);
|
|
485
|
+
__decorate([
|
|
486
|
+
property({ type: Object })
|
|
487
|
+
], BaseChartWidget.prototype, "dataParams", void 0);
|
|
488
|
+
__decorate([
|
|
489
|
+
property({ type: Object })
|
|
490
|
+
], BaseChartWidget.prototype, "dashboard", void 0);
|
|
491
|
+
__decorate([
|
|
492
|
+
property({ type: Object })
|
|
493
|
+
], BaseChartWidget.prototype, "data", void 0);
|
|
494
|
+
__decorate([
|
|
495
|
+
property({ type: Object })
|
|
496
|
+
], BaseChartWidget.prototype, "meta", void 0);
|
|
497
|
+
__decorate([
|
|
498
|
+
property({ type: Object })
|
|
499
|
+
], BaseChartWidget.prototype, "options", void 0);
|
|
500
|
+
__decorate([
|
|
501
|
+
property({ type: Boolean })
|
|
502
|
+
], BaseChartWidget.prototype, "lazy", void 0);
|
|
503
|
+
__decorate([
|
|
504
|
+
property({ type: String, attribute: 'prefetch-margin' })
|
|
505
|
+
], BaseChartWidget.prototype, "prefetchMargin", void 0);
|
|
506
|
+
__decorate([
|
|
507
|
+
property({ type: Object })
|
|
508
|
+
], BaseChartWidget.prototype, "initialDefinition", void 0);
|
|
509
|
+
__decorate([
|
|
510
|
+
state()
|
|
511
|
+
], BaseChartWidget.prototype, "_loading", void 0);
|
|
512
|
+
__decorate([
|
|
513
|
+
state()
|
|
514
|
+
], BaseChartWidget.prototype, "_error", void 0);
|
|
515
|
+
__decorate([
|
|
516
|
+
state()
|
|
517
|
+
], BaseChartWidget.prototype, "_dashboardParams", void 0);
|
|
518
|
+
__decorate([
|
|
519
|
+
state()
|
|
520
|
+
], BaseChartWidget.prototype, "_widgetFilters", void 0);
|
|
521
|
+
__decorate([
|
|
522
|
+
state()
|
|
523
|
+
], BaseChartWidget.prototype, "_widgetFilterParams", void 0);
|
|
524
|
+
__decorate([
|
|
525
|
+
state()
|
|
526
|
+
], BaseChartWidget.prototype, "_hideFilter", void 0);
|
|
527
|
+
__decorate([
|
|
528
|
+
state()
|
|
529
|
+
], BaseChartWidget.prototype, "_widgetData", void 0);
|
|
530
|
+
__decorate([
|
|
531
|
+
state()
|
|
532
|
+
], BaseChartWidget.prototype, "_viewportVisible", void 0);
|
|
533
|
+
__decorate([
|
|
534
|
+
state()
|
|
535
|
+
], BaseChartWidget.prototype, "_definitionTitle", void 0);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { BaseChartWidget } from './base-chart.js';
|
|
2
|
+
export { BarChartWidget } from './bar-chart.js';
|
|
3
|
+
export { LineChartWidget } from './line-chart.js';
|
|
4
|
+
export { PieChartWidget } from './pie-chart.js';
|
|
5
|
+
export { TableWidget } from './table.js';
|
|
6
|
+
export { AnalyticsWidget } from './analytics-widget.js';
|
|
7
|
+
export { AnalyticsReport } from './analytics-report.js';
|
|
8
|
+
import './bar-chart.js';
|
|
9
|
+
import './line-chart.js';
|
|
10
|
+
import './pie-chart.js';
|
|
11
|
+
import './table.js';
|
|
12
|
+
import './analytics-widget.js';
|
|
13
|
+
import './analytics-report.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,gBAAgB,CAAC;AACxB,OAAO,iBAAiB,CAAC;AACzB,OAAO,gBAAgB,CAAC;AACxB,OAAO,YAAY,CAAC;AACpB,OAAO,uBAAuB,CAAC;AAC/B,OAAO,uBAAuB,CAAC"}
|
package/widgets/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { BaseChartWidget } from './base-chart.js';
|
|
2
|
+
export { BarChartWidget } from './bar-chart.js';
|
|
3
|
+
export { LineChartWidget } from './line-chart.js';
|
|
4
|
+
export { PieChartWidget } from './pie-chart.js';
|
|
5
|
+
export { TableWidget } from './table.js';
|
|
6
|
+
export { AnalyticsWidget } from './analytics-widget.js';
|
|
7
|
+
export { AnalyticsReport } from './analytics-report.js';
|
|
8
|
+
// Register custom elements so they work when imported (e.g. in designer/dashboard)
|
|
9
|
+
import './bar-chart.js';
|
|
10
|
+
import './line-chart.js';
|
|
11
|
+
import './pie-chart.js';
|
|
12
|
+
import './table.js';
|
|
13
|
+
import './analytics-widget.js';
|
|
14
|
+
import './analytics-report.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EChartsOption } from 'echarts/types/dist/option';
|
|
2
|
+
import { BaseChartWidget } from './base-chart.js';
|
|
3
|
+
/**
|
|
4
|
+
* Line chart widget (Apache ECharts). Use data-url or inline data.
|
|
5
|
+
* When widgetData is present (from API), use its categories + series so x-axis is correct (e.g. split_by).
|
|
6
|
+
* Otherwise first column = categories, remaining = series.
|
|
7
|
+
*/
|
|
8
|
+
export declare class LineChartWidget extends BaseChartWidget {
|
|
9
|
+
buildOption(data: Record<string, unknown>[], meta: Record<string, {
|
|
10
|
+
type?: string;
|
|
11
|
+
}>, widgetData?: unknown): EChartsOption;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=line-chart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-chart.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/line-chart.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAuBlD;;;;GAIG;AACH,qBACa,eAAgB,SAAQ,eAAe;IACzC,WAAW,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EACvC,UAAU,CAAC,EAAE,OAAO,GACnB,aAAa;CAgDjB"}
|