@acorex/platform 19.3.0 → 19.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.
- package/common/lib/utils/index.d.ts +0 -1
- package/fesm2022/acorex-platform-common.mjs +1 -134
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +247 -0
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/{acorex-platform-widgets-tabular-data-edit-popup.component-CybYV1Kf.mjs → acorex-platform-widgets-tabular-data-edit-popup.component-1IseEVXQ.mjs} +2 -2
- package/fesm2022/acorex-platform-widgets-tabular-data-edit-popup.component-1IseEVXQ.mjs.map +1 -0
- package/fesm2022/acorex-platform-widgets.mjs +8 -4395
- package/fesm2022/acorex-platform-widgets.mjs.map +1 -1
- package/layout/components/README.md +3 -0
- package/layout/components/index.d.ts +1 -0
- package/layout/components/lib/user-avatar/index.d.ts +4 -0
- package/layout/components/lib/user-avatar/user-avatar.component.d.ts +27 -0
- package/layout/components/lib/user-avatar/user-avatar.provider.d.ts +3 -0
- package/layout/components/lib/user-avatar/user-avatar.service.d.ts +42 -0
- package/layout/components/lib/user-avatar/user-avatar.types.d.ts +12 -0
- package/package.json +5 -1
- package/widgets/lib/widgets/index.d.ts +0 -11
- package/common/lib/utils/data-generator.d.ts +0 -26
- package/fesm2022/acorex-platform-widgets-tabular-data-edit-popup.component-CybYV1Kf.mjs.map +0 -1
- package/widgets/lib/widgets/charts/bar-chart/bar-chart-widget.component.d.ts +0 -72
- package/widgets/lib/widgets/charts/bar-chart/bar-chart-widget.config.d.ts +0 -7
- package/widgets/lib/widgets/charts/bar-chart/bar-chart.type.d.ts +0 -34
- package/widgets/lib/widgets/charts/bar-chart/index.d.ts +0 -2
- package/widgets/lib/widgets/charts/chart.type.d.ts +0 -3
- package/widgets/lib/widgets/charts/clock-calendar/clock-calendar-widget.component.d.ts +0 -40
- package/widgets/lib/widgets/charts/clock-calendar/clock-calendar-widget.config.d.ts +0 -7
- package/widgets/lib/widgets/charts/clock-calendar/clock-calendar.types.d.ts +0 -50
- package/widgets/lib/widgets/charts/clock-calendar/index.d.ts +0 -3
- package/widgets/lib/widgets/charts/donut-chart/donut-chart-widget.component.d.ts +0 -58
- package/widgets/lib/widgets/charts/donut-chart/donut-chart-widget.config.d.ts +0 -7
- package/widgets/lib/widgets/charts/donut-chart/donut-chart.type.d.ts +0 -67
- package/widgets/lib/widgets/charts/donut-chart/index.d.ts +0 -2
- package/widgets/lib/widgets/charts/gauge-chart/gauge-chart-widget.component.d.ts +0 -75
- package/widgets/lib/widgets/charts/gauge-chart/gauge-chart-widget.config.d.ts +0 -7
- package/widgets/lib/widgets/charts/gauge-chart/gauge-chart.type.d.ts +0 -29
- package/widgets/lib/widgets/charts/gauge-chart/index.d.ts +0 -3
- package/widgets/lib/widgets/charts/index.d.ts +0 -11
- package/widgets/lib/widgets/charts/line-chart/index.d.ts +0 -3
- package/widgets/lib/widgets/charts/line-chart/line-chart-widget.component.d.ts +0 -76
- package/widgets/lib/widgets/charts/line-chart/line-chart-widget.config.d.ts +0 -7
- package/widgets/lib/widgets/charts/line-chart/line-chart.type.d.ts +0 -41
- package/widgets/lib/widgets/charts/notification/index.d.ts +0 -3
- package/widgets/lib/widgets/charts/notification/notification-widget.component.d.ts +0 -54
- package/widgets/lib/widgets/charts/notification/notification-widget.config.d.ts +0 -10
- package/widgets/lib/widgets/charts/notification/notification.type.d.ts +0 -47
- package/widgets/lib/widgets/charts/shared/chart-base.component.d.ts +0 -44
- package/widgets/lib/widgets/charts/shared/chart-base.type.d.ts +0 -37
- package/widgets/lib/widgets/charts/shared/components/chart-tooltip/chart-tooltip.component.d.ts +0 -28
- package/widgets/lib/widgets/charts/shared/components/chart-tooltip/index.d.ts +0 -1
- package/widgets/lib/widgets/charts/shared/index.d.ts +0 -3
- package/widgets/lib/widgets/charts/sticky-note/index.d.ts +0 -2
- package/widgets/lib/widgets/charts/sticky-note/sticky-note-widget.component.d.ts +0 -21
- package/widgets/lib/widgets/charts/sticky-note/sticky-note-widget.config.d.ts +0 -7
- package/widgets/lib/widgets/charts/tasklist/index.d.ts +0 -3
- package/widgets/lib/widgets/charts/tasklist/tasklist-widget.component.d.ts +0 -34
- package/widgets/lib/widgets/charts/tasklist/tasklist-widget.config.d.ts +0 -7
- package/widgets/lib/widgets/charts/tasklist/tasklist.type.d.ts +0 -36
- package/widgets/lib/widgets/charts/weather/index.d.ts +0 -3
- package/widgets/lib/widgets/charts/weather/weather-services/index.d.ts +0 -3
- package/widgets/lib/widgets/charts/weather/weather-services/weather-api.abstract.d.ts +0 -174
- package/widgets/lib/widgets/charts/weather/weather-services/weather-api.key.d.ts +0 -2
- package/widgets/lib/widgets/charts/weather/weather-services/weather-api.mock.service.d.ts +0 -47
- package/widgets/lib/widgets/charts/weather/weather-services/weather-api.service.d.ts +0 -48
- package/widgets/lib/widgets/charts/weather/weather-widget.component.d.ts +0 -109
- package/widgets/lib/widgets/charts/weather/weather-widget.config.d.ts +0 -14
|
@@ -10,9 +10,9 @@ import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
|
10
10
|
import * as i2 from '@acorex/components/loading';
|
|
11
11
|
import { AXLoadingModule } from '@acorex/components/loading';
|
|
12
12
|
import * as i1$1 from '@angular/common';
|
|
13
|
-
import { CommonModule
|
|
13
|
+
import { CommonModule } from '@angular/common';
|
|
14
14
|
import * as i0 from '@angular/core';
|
|
15
|
-
import { computed, EventEmitter, ChangeDetectionStrategy, Component, inject, afterNextRender, HostBinding, signal, ViewEncapsulation, InjectionToken, effect, ViewChild, untracked, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, viewChild, ElementRef, afterRender, NgZone, model, input, linkedSignal,
|
|
15
|
+
import { computed, EventEmitter, ChangeDetectionStrategy, Component, inject, afterNextRender, HostBinding, signal, ViewEncapsulation, InjectionToken, effect, ViewChild, untracked, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, viewChild, ElementRef, afterRender, NgZone, model, input, linkedSignal, HostListener, importProvidersFrom, NgModule } from '@angular/core';
|
|
16
16
|
import * as i1 from '@acorex/components/check-box';
|
|
17
17
|
import { AXCheckBoxModule } from '@acorex/components/check-box';
|
|
18
18
|
import * as i4 from '@acorex/components/form';
|
|
@@ -31,7 +31,7 @@ import { AXTextBoxModule } from '@acorex/components/text-box';
|
|
|
31
31
|
import * as i5$1 from '@acorex/core/translation';
|
|
32
32
|
import { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';
|
|
33
33
|
import { AXBasePageComponent } from '@acorex/components/page';
|
|
34
|
-
import { AXDateTimeFormatter,
|
|
34
|
+
import { AXDateTimeFormatter, AXCalendarService } from '@acorex/core/date-time';
|
|
35
35
|
import * as i3$2 from '@acorex/components/datetime-box';
|
|
36
36
|
import { AXDateTimeBoxModule } from '@acorex/components/datetime-box';
|
|
37
37
|
import * as i5$2 from '@acorex/components/text-area';
|
|
@@ -39,8 +39,7 @@ import { AXTextAreaModule } from '@acorex/components/text-area';
|
|
|
39
39
|
import { set, isNumber, castArray, cloneDeep, isEqual, sum, get } from 'lodash-es';
|
|
40
40
|
import * as i4$1 from '@acorex/components/tabs';
|
|
41
41
|
import { AXTabsModule } from '@acorex/components/tabs';
|
|
42
|
-
import
|
|
43
|
-
import { AXFormatService, AXFormatModule } from '@acorex/core/format';
|
|
42
|
+
import { AXFormatService } from '@acorex/core/format';
|
|
44
43
|
import * as i1$4 from '@acorex/components/number-box';
|
|
45
44
|
import { AXNumberBoxModule } from '@acorex/components/number-box';
|
|
46
45
|
import * as i3$3 from '@acorex/components/password-box';
|
|
@@ -55,7 +54,7 @@ import * as i5$3 from '@acorex/components/search-box';
|
|
|
55
54
|
import { AXSearchBoxModule } from '@acorex/components/search-box';
|
|
56
55
|
import * as i2$4 from '@acorex/components/selection-list';
|
|
57
56
|
import { AXSelectionListModule } from '@acorex/components/selection-list';
|
|
58
|
-
import { first, Subscription
|
|
57
|
+
import { first, Subscription } from 'rxjs';
|
|
59
58
|
import { AXFileService } from '@acorex/core/file';
|
|
60
59
|
import * as i6 from '@acorex/components/dropdown';
|
|
61
60
|
import { AXDropdownModule } from '@acorex/components/dropdown';
|
|
@@ -71,7 +70,7 @@ import { AXImageModule } from '@acorex/components/image';
|
|
|
71
70
|
import * as i1$6 from '@acorex/components/map';
|
|
72
71
|
import { AXMapModule } from '@acorex/components/map';
|
|
73
72
|
import * as i1$7 from '@acorex/components/grid-layout-builder';
|
|
74
|
-
import { AXGridLayoutContainerComponent, AXGridLayoutBuilderModule
|
|
73
|
+
import { AXGridLayoutContainerComponent, AXGridLayoutBuilderModule } from '@acorex/components/grid-layout-builder';
|
|
75
74
|
import { AXPDesignerService, AXPWidgetDesignerRendererDirective, AXPDesignerGridDrawerComponent, AXPDesignerAddWidgetMiniButtonComponent } from '@acorex/platform/layout/designer';
|
|
76
75
|
import * as i1$8 from '@acorex/components/button-group';
|
|
77
76
|
import { AXButtonGroupModule } from '@acorex/components/button-group';
|
|
@@ -81,10 +80,6 @@ import * as i2$8 from '@acorex/components/color-box';
|
|
|
81
80
|
import { AXColorBoxModule } from '@acorex/components/color-box';
|
|
82
81
|
import * as i1$9 from '@acorex/components/popover';
|
|
83
82
|
import { AXPopoverComponent, AXPopoverModule } from '@acorex/components/popover';
|
|
84
|
-
import { AXTagModule } from '@acorex/components/tag';
|
|
85
|
-
import * as i5$4 from '@acorex/components/avatar';
|
|
86
|
-
import { AXAvatarModule } from '@acorex/components/avatar';
|
|
87
|
-
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
|
88
83
|
import * as i1$a from '@acorex/components/cron-job';
|
|
89
84
|
import { AXCronJobModule } from '@acorex/components/cron-job';
|
|
90
85
|
import * as i1$b from '@acorex/components/qrcode';
|
|
@@ -12391,4368 +12386,6 @@ const AXPRequiredValidationWidget = {
|
|
|
12391
12386
|
},
|
|
12392
12387
|
};
|
|
12393
12388
|
|
|
12394
|
-
/**
|
|
12395
|
-
* Common base types and utilities for all chart components
|
|
12396
|
-
*/
|
|
12397
|
-
/**
|
|
12398
|
-
* Color utility functions for charts
|
|
12399
|
-
*/
|
|
12400
|
-
const AXPChartColors = {
|
|
12401
|
-
// Modern color palette suitable for data visualization
|
|
12402
|
-
defaultColors: [
|
|
12403
|
-
'#4361ee', // Blue
|
|
12404
|
-
'#3a0ca3', // Purple
|
|
12405
|
-
'#7209b7', // Violet
|
|
12406
|
-
'#f72585', // Pink
|
|
12407
|
-
'#4cc9f0', // Light Blue
|
|
12408
|
-
'#4895ef', // Sky Blue
|
|
12409
|
-
'#560bad', // Deep Purple
|
|
12410
|
-
'#f15bb5', // Light Pink
|
|
12411
|
-
'#00bbf9', // Cyan
|
|
12412
|
-
'#00f5d4', // Teal
|
|
12413
|
-
],
|
|
12414
|
-
// Get a color from the palette by index with wraparound
|
|
12415
|
-
getColor: (index, customPalette) => {
|
|
12416
|
-
const palette = customPalette || AXPChartColors.defaultColors;
|
|
12417
|
-
return palette[index % palette.length];
|
|
12418
|
-
},
|
|
12419
|
-
// Generate a continuous color palette from a base color
|
|
12420
|
-
generatePalette: (baseColor, count) => {
|
|
12421
|
-
// Simple implementation - in real usage you might want a more sophisticated algorithm
|
|
12422
|
-
const colors = [];
|
|
12423
|
-
for (let i = 0; i < count; i++) {
|
|
12424
|
-
// This is simplistic - a real implementation would use HSL or other color manipulation
|
|
12425
|
-
const opacity = 0.4 + (0.6 * i) / count;
|
|
12426
|
-
colors.push(`${baseColor}${Math.floor(opacity * 255)
|
|
12427
|
-
.toString(16)
|
|
12428
|
-
.padStart(2, '0')}`);
|
|
12429
|
-
}
|
|
12430
|
-
return colors;
|
|
12431
|
-
},
|
|
12432
|
-
};
|
|
12433
|
-
/**
|
|
12434
|
-
* Shared utility for loading D3.js dynamically
|
|
12435
|
-
*/
|
|
12436
|
-
async function loadD3() {
|
|
12437
|
-
try {
|
|
12438
|
-
// Dynamic import of d3 without relying on d3 being imported at the top
|
|
12439
|
-
return await import('d3');
|
|
12440
|
-
}
|
|
12441
|
-
catch (error) {
|
|
12442
|
-
console.error('Failed to load D3.js:', error);
|
|
12443
|
-
throw error;
|
|
12444
|
-
}
|
|
12445
|
-
}
|
|
12446
|
-
|
|
12447
|
-
/**
|
|
12448
|
-
* Base component class for all chart components with common chart functionality
|
|
12449
|
-
*/
|
|
12450
|
-
class AXPChartBaseComponent extends AXPValueWidgetComponent {
|
|
12451
|
-
// Constructor with protected change detector
|
|
12452
|
-
constructor(cdr) {
|
|
12453
|
-
super();
|
|
12454
|
-
this.cdr = cdr;
|
|
12455
|
-
// Get injector for running effects
|
|
12456
|
-
this.injector = inject(Injector);
|
|
12457
|
-
// Track component lifecycle
|
|
12458
|
-
this.isInitialized = signal(false);
|
|
12459
|
-
this.isRendered = signal(false);
|
|
12460
|
-
// Internal chart data storage with fallback logic
|
|
12461
|
-
this._internalData = signal(null);
|
|
12462
|
-
// Accessor for chart data with fallback to getValue() or defaultValue
|
|
12463
|
-
this.chartData = computed(() => {
|
|
12464
|
-
if (this._internalData()) {
|
|
12465
|
-
return this._internalData();
|
|
12466
|
-
}
|
|
12467
|
-
return this.getValue() || this.defaultValue;
|
|
12468
|
-
});
|
|
12469
|
-
// Options tracker for detecting changes
|
|
12470
|
-
this._lastOptionsSnapshot = '';
|
|
12471
|
-
// Store the effect cleanup function
|
|
12472
|
-
this.effectRef = null;
|
|
12473
|
-
}
|
|
12474
|
-
ngOnInit() {
|
|
12475
|
-
super.ngOnInit();
|
|
12476
|
-
this.loadD3();
|
|
12477
|
-
}
|
|
12478
|
-
ngAfterViewInit() {
|
|
12479
|
-
this.isRendered.set(true);
|
|
12480
|
-
this.setupEffects();
|
|
12481
|
-
}
|
|
12482
|
-
ngOnDestroy() {
|
|
12483
|
-
// Clean up effect if it exists
|
|
12484
|
-
if (this.effectRef) {
|
|
12485
|
-
this.effectRef.destroy();
|
|
12486
|
-
}
|
|
12487
|
-
this.cleanupChart();
|
|
12488
|
-
}
|
|
12489
|
-
/**
|
|
12490
|
-
* Load D3.js library asynchronously
|
|
12491
|
-
*/
|
|
12492
|
-
async loadD3() {
|
|
12493
|
-
try {
|
|
12494
|
-
this.d3 = await loadD3();
|
|
12495
|
-
this.isInitialized.set(true);
|
|
12496
|
-
// Initialize chart data once D3 is loaded
|
|
12497
|
-
const initialData = this.getValue() || this.defaultValue;
|
|
12498
|
-
if (initialData) {
|
|
12499
|
-
this._internalData.set(initialData);
|
|
12500
|
-
}
|
|
12501
|
-
// Create chart once D3 is loaded and if we're already rendered
|
|
12502
|
-
if (this.isRendered()) {
|
|
12503
|
-
this.createChart();
|
|
12504
|
-
}
|
|
12505
|
-
}
|
|
12506
|
-
catch (error) {
|
|
12507
|
-
console.error('Error loading D3.js:', error);
|
|
12508
|
-
}
|
|
12509
|
-
}
|
|
12510
|
-
/**
|
|
12511
|
-
* Set up reactive effects to track data and option changes
|
|
12512
|
-
*/
|
|
12513
|
-
setupEffects() {
|
|
12514
|
-
// Run effect in injection context to avoid NG0203 error
|
|
12515
|
-
this.effectRef = runInInjectionContext(this.injector, () => {
|
|
12516
|
-
return effect(() => {
|
|
12517
|
-
// Only update if D3 is loaded and component is rendered
|
|
12518
|
-
if (!this.isInitialized() || !this.isRendered())
|
|
12519
|
-
return;
|
|
12520
|
-
// Track dependencies explicitly
|
|
12521
|
-
const data = this.getValue();
|
|
12522
|
-
const options = this.options();
|
|
12523
|
-
// Store current options snapshot for comparison
|
|
12524
|
-
const currentOptions = JSON.stringify(options);
|
|
12525
|
-
const optionsChanged = currentOptions !== this._lastOptionsSnapshot;
|
|
12526
|
-
this._lastOptionsSnapshot = currentOptions;
|
|
12527
|
-
// Check if data changed
|
|
12528
|
-
const dataChanged = data && JSON.stringify(data) !== JSON.stringify(this._internalData());
|
|
12529
|
-
// Update internal data if it changed
|
|
12530
|
-
if (dataChanged && data) {
|
|
12531
|
-
this._internalData.set(data);
|
|
12532
|
-
}
|
|
12533
|
-
// Update chart if either data or options changed
|
|
12534
|
-
if (dataChanged || optionsChanged) {
|
|
12535
|
-
this.updateChart();
|
|
12536
|
-
}
|
|
12537
|
-
});
|
|
12538
|
-
});
|
|
12539
|
-
}
|
|
12540
|
-
/**
|
|
12541
|
-
* Get dimensions of the container element
|
|
12542
|
-
*/
|
|
12543
|
-
getContainerDimensions(containerElement) {
|
|
12544
|
-
if (!containerElement?.nativeElement) {
|
|
12545
|
-
return { width: 300, height: 300 }; // Default fallback
|
|
12546
|
-
}
|
|
12547
|
-
const { clientWidth, clientHeight } = containerElement.nativeElement;
|
|
12548
|
-
return {
|
|
12549
|
-
width: clientWidth || 300,
|
|
12550
|
-
height: clientHeight || 300,
|
|
12551
|
-
};
|
|
12552
|
-
}
|
|
12553
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPChartBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
12554
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.3", type: AXPChartBaseComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
12555
|
-
}
|
|
12556
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPChartBaseComponent, decorators: [{
|
|
12557
|
-
type: Directive
|
|
12558
|
-
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
|
|
12559
|
-
|
|
12560
|
-
class AXPChartTooltipComponent {
|
|
12561
|
-
constructor() {
|
|
12562
|
-
this.data = input(null);
|
|
12563
|
-
this.position = input({ x: 0, y: 0 });
|
|
12564
|
-
this.visible = input(false);
|
|
12565
|
-
/**
|
|
12566
|
-
* Whether to show the tooltip's percentage badge
|
|
12567
|
-
*/
|
|
12568
|
-
this.showPercentage = input(true);
|
|
12569
|
-
/**
|
|
12570
|
-
* Optional custom styling for the tooltip
|
|
12571
|
-
*/
|
|
12572
|
-
this.style = input({});
|
|
12573
|
-
}
|
|
12574
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPChartTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
12575
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: AXPChartTooltipComponent, isStandalone: true, selector: "ax-chart-tooltip", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, showPercentage: { classPropertyName: "showPercentage", publicName: "showPercentage", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (visible() && data()) {\n<div class=\"chart-tooltip\" [style.left.px]=\"position().x\" [style.top.px]=\"position().y\" [ngStyle]=\"style()\">\n <div class=\"chart-tooltip-title\">{{ data()!.title }}</div>\n <div class=\"chart-tooltip-content\">\n @if (data()!.color) {\n <div class=\"chart-tooltip-color\" [style.background-color]=\"data()!.color\"></div>\n }\n <div class=\"chart-tooltip-value\">{{ data()!.value }}</div>\n @if (showPercentage() && data()!.percentage) {\n <div class=\"chart-tooltip-percentage\">{{ data()!.percentage }}</div>\n }\n </div>\n</div>\n}\n", styles: [".chart-tooltip{position:absolute;pointer-events:none;background-color:rgba(33,33,33,.9);color:#fff;padding:.5rem .75rem;border-radius:.375rem;font-size:.8rem;z-index:10;box-shadow:0 4px 12px rgba(0,0,0,.15);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border:1px solid rgba(255,255,255,.1);transform:translate(10px,-50%);max-width:200px;font-family:var(--ax-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);transition:opacity .15s ease,transform .15s ease}.chart-tooltip-title{font-weight:600;padding-bottom:.5rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chart-tooltip-content{display:flex;justify-content:space-between;align-items:center;gap:.5rem}.chart-tooltip-color{width:10px;height:10px;border-radius:2px;flex-shrink:0;box-shadow:0 1px 2px rgba(0,0,0,.2)}.chart-tooltip-value{font-weight:500;flex-grow:1}.chart-tooltip-percentage{background-color:rgba(255,255,255,.2);padding:.125rem .375rem;border-radius:1rem;font-size:.7rem;font-weight:500;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
12576
|
-
}
|
|
12577
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPChartTooltipComponent, decorators: [{
|
|
12578
|
-
type: Component,
|
|
12579
|
-
args: [{ selector: 'ax-chart-tooltip', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible() && data()) {\n<div class=\"chart-tooltip\" [style.left.px]=\"position().x\" [style.top.px]=\"position().y\" [ngStyle]=\"style()\">\n <div class=\"chart-tooltip-title\">{{ data()!.title }}</div>\n <div class=\"chart-tooltip-content\">\n @if (data()!.color) {\n <div class=\"chart-tooltip-color\" [style.background-color]=\"data()!.color\"></div>\n }\n <div class=\"chart-tooltip-value\">{{ data()!.value }}</div>\n @if (showPercentage() && data()!.percentage) {\n <div class=\"chart-tooltip-percentage\">{{ data()!.percentage }}</div>\n }\n </div>\n</div>\n}\n", styles: [".chart-tooltip{position:absolute;pointer-events:none;background-color:rgba(33,33,33,.9);color:#fff;padding:.5rem .75rem;border-radius:.375rem;font-size:.8rem;z-index:10;box-shadow:0 4px 12px rgba(0,0,0,.15);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border:1px solid rgba(255,255,255,.1);transform:translate(10px,-50%);max-width:200px;font-family:var(--ax-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);transition:opacity .15s ease,transform .15s ease}.chart-tooltip-title{font-weight:600;padding-bottom:.5rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chart-tooltip-content{display:flex;justify-content:space-between;align-items:center;gap:.5rem}.chart-tooltip-color{width:10px;height:10px;border-radius:2px;flex-shrink:0;box-shadow:0 1px 2px rgba(0,0,0,.2)}.chart-tooltip-value{font-weight:500;flex-grow:1}.chart-tooltip-percentage{background-color:rgba(255,255,255,.2);padding:.125rem .375rem;border-radius:1rem;font-size:.7rem;font-weight:500;flex-shrink:0}\n"] }]
|
|
12580
|
-
}] });
|
|
12581
|
-
|
|
12582
|
-
/**
|
|
12583
|
-
* Bar Chart Widget Component
|
|
12584
|
-
* Renders data as vertical bars with interactive hover effects and animations
|
|
12585
|
-
*/
|
|
12586
|
-
class AXPBarChartWidgetViewComponent extends AXPChartBaseComponent {
|
|
12587
|
-
constructor() {
|
|
12588
|
-
super(...arguments);
|
|
12589
|
-
this.barClick = output();
|
|
12590
|
-
// Chart container reference
|
|
12591
|
-
this.chartContainerEl = viewChild.required('chartContainer');
|
|
12592
|
-
this.margin = { top: 20, right: 20, bottom: 30, left: 40 };
|
|
12593
|
-
// Tooltip state
|
|
12594
|
-
this._tooltipVisible = signal(false);
|
|
12595
|
-
this._tooltipPosition = signal({ x: 0, y: 0 });
|
|
12596
|
-
this._tooltipData = signal({
|
|
12597
|
-
title: '',
|
|
12598
|
-
value: '0',
|
|
12599
|
-
percentage: '0%',
|
|
12600
|
-
color: '',
|
|
12601
|
-
});
|
|
12602
|
-
// Tooltip accessors
|
|
12603
|
-
this.tooltipVisible = computed(() => this._tooltipVisible());
|
|
12604
|
-
this.tooltipPosition = computed(() => this._tooltipPosition());
|
|
12605
|
-
this.tooltipData = computed(() => this._tooltipData());
|
|
12606
|
-
// Bar appearance options
|
|
12607
|
-
this.barWidth = computed(() => this.options()['barWidth'] ?? 80);
|
|
12608
|
-
this.cornerRadius = computed(() => this.options()['cornerRadius'] ?? 4);
|
|
12609
|
-
}
|
|
12610
|
-
// Chart lifecycle methods
|
|
12611
|
-
/**
|
|
12612
|
-
* Creates the bar chart SVG and renders all elements
|
|
12613
|
-
*/
|
|
12614
|
-
createChart() {
|
|
12615
|
-
if (!this.d3 || !this.chartContainerEl()?.nativeElement)
|
|
12616
|
-
return;
|
|
12617
|
-
const containerElement = this.chartContainerEl().nativeElement;
|
|
12618
|
-
const data = this.chartData() || [];
|
|
12619
|
-
// Clear existing chart
|
|
12620
|
-
this.d3.select(containerElement).selectAll('svg').remove();
|
|
12621
|
-
// Early return if no data
|
|
12622
|
-
if (!data.length) {
|
|
12623
|
-
this.showNoDataMessage(containerElement);
|
|
12624
|
-
return;
|
|
12625
|
-
}
|
|
12626
|
-
// Get options and setup dimensions
|
|
12627
|
-
const options = this.options();
|
|
12628
|
-
this.setupDimensions(containerElement, options);
|
|
12629
|
-
// No need to create SVG here as it's now done in setupDimensions
|
|
12630
|
-
// Create scales and axes
|
|
12631
|
-
this.setupScales(data);
|
|
12632
|
-
this.createAxes(options);
|
|
12633
|
-
// Render the bars
|
|
12634
|
-
this.renderBars(data);
|
|
12635
|
-
}
|
|
12636
|
-
updateChart() {
|
|
12637
|
-
this.createChart();
|
|
12638
|
-
}
|
|
12639
|
-
cleanupChart() {
|
|
12640
|
-
if (this.svg) {
|
|
12641
|
-
this.d3.select(this.chartContainerEl()?.nativeElement).selectAll('svg').remove();
|
|
12642
|
-
this.svg = null;
|
|
12643
|
-
this.chart = null;
|
|
12644
|
-
}
|
|
12645
|
-
this._tooltipVisible.set(false);
|
|
12646
|
-
}
|
|
12647
|
-
// Private chart creation methods
|
|
12648
|
-
/**
|
|
12649
|
-
* Sets up chart dimensions and creates SVG with responsive attributes
|
|
12650
|
-
*/
|
|
12651
|
-
setupDimensions(containerElement, options) {
|
|
12652
|
-
// Get container dimensions
|
|
12653
|
-
const containerWidth = containerElement.clientWidth;
|
|
12654
|
-
const containerHeight = containerElement.clientHeight;
|
|
12655
|
-
// If options specify width and height, use those, otherwise default to container size
|
|
12656
|
-
const minDim = Math.min(200, containerWidth, containerHeight); // Ensure reasonable minimum
|
|
12657
|
-
if (options.width && options.height) {
|
|
12658
|
-
// Explicit dimensions provided
|
|
12659
|
-
this.width = options.width - this.margin.left - this.margin.right;
|
|
12660
|
-
this.height = options.height - this.margin.top - this.margin.bottom;
|
|
12661
|
-
}
|
|
12662
|
-
else {
|
|
12663
|
-
// Responsive dimensions
|
|
12664
|
-
this.width = Math.max(containerWidth, minDim) - this.margin.left - this.margin.right;
|
|
12665
|
-
this.height = Math.max(containerHeight, minDim) - this.margin.top - this.margin.bottom;
|
|
12666
|
-
}
|
|
12667
|
-
// Create responsive SVG that scales with its container
|
|
12668
|
-
const svg = this.d3
|
|
12669
|
-
.select(containerElement)
|
|
12670
|
-
.append('svg')
|
|
12671
|
-
.attr('width', '100%')
|
|
12672
|
-
.attr('height', '100%')
|
|
12673
|
-
.attr('viewBox', `0 0 ${this.width + this.margin.left + this.margin.right} ${this.height + this.margin.top + this.margin.bottom}`)
|
|
12674
|
-
.attr('preserveAspectRatio', 'xMidYMid meet');
|
|
12675
|
-
this.svg = svg;
|
|
12676
|
-
// Create chart group with margins
|
|
12677
|
-
this.chart = this.svg.append('g').attr('transform', `translate(${this.margin.left},${this.margin.top})`);
|
|
12678
|
-
}
|
|
12679
|
-
/**
|
|
12680
|
-
* Creates x and y scales for the chart
|
|
12681
|
-
*/
|
|
12682
|
-
setupScales(data) {
|
|
12683
|
-
// Get the bar width percentage (default 80%)
|
|
12684
|
-
const barWidthPercent = this.barWidth() / 100;
|
|
12685
|
-
// Calculate padding based on barWidth (inverse relationship)
|
|
12686
|
-
const padding = Math.max(0.1, 1 - barWidthPercent);
|
|
12687
|
-
// Create x scale (band scale for categorical data)
|
|
12688
|
-
this.xScale = this.d3
|
|
12689
|
-
.scaleBand()
|
|
12690
|
-
.domain(data.map((d) => d.label))
|
|
12691
|
-
.range([0, this.width])
|
|
12692
|
-
.padding(padding);
|
|
12693
|
-
// Create y scale (linear scale for values)
|
|
12694
|
-
this.yScale = this.d3
|
|
12695
|
-
.scaleLinear()
|
|
12696
|
-
.domain([0, this.d3.max(data, (d) => d.value) || 0])
|
|
12697
|
-
.nice()
|
|
12698
|
-
.range([this.height, 0]);
|
|
12699
|
-
}
|
|
12700
|
-
/**
|
|
12701
|
-
* Creates x and y axes with grid lines
|
|
12702
|
-
*/
|
|
12703
|
-
createAxes(options) {
|
|
12704
|
-
// Only create axes if they are enabled in options
|
|
12705
|
-
const showXAxis = options.showXAxis !== false;
|
|
12706
|
-
const showYAxis = options.showYAxis !== false;
|
|
12707
|
-
const showGrid = options.showGrid !== false;
|
|
12708
|
-
if (showXAxis) {
|
|
12709
|
-
// Create X axis
|
|
12710
|
-
this.xAxis = this.chart
|
|
12711
|
-
.append('g')
|
|
12712
|
-
.attr('class', 'axp-bar-chart-axis-x')
|
|
12713
|
-
.attr('transform', `translate(0,${this.height})`)
|
|
12714
|
-
.call(this.d3.axisBottom(this.xScale));
|
|
12715
|
-
}
|
|
12716
|
-
if (showYAxis) {
|
|
12717
|
-
// Create Y axis
|
|
12718
|
-
this.yAxis = this.chart.append('g').attr('class', 'axp-bar-chart-axis-y').call(this.d3.axisLeft(this.yScale));
|
|
12719
|
-
}
|
|
12720
|
-
if (showGrid) {
|
|
12721
|
-
// Add horizontal grid lines
|
|
12722
|
-
this.chart
|
|
12723
|
-
.append('g')
|
|
12724
|
-
.attr('class', 'axp-bar-chart-grid')
|
|
12725
|
-
.call(this.d3
|
|
12726
|
-
.axisLeft(this.yScale)
|
|
12727
|
-
.tickSize(-this.width)
|
|
12728
|
-
.tickFormat(() => ''))
|
|
12729
|
-
.selectAll('.tick')
|
|
12730
|
-
.style('color', 'rgb(153 153 153 / 30%)'); // Add gray color to ticks
|
|
12731
|
-
}
|
|
12732
|
-
}
|
|
12733
|
-
/**
|
|
12734
|
-
* Renders the bars with animations
|
|
12735
|
-
*/
|
|
12736
|
-
renderBars(data) {
|
|
12737
|
-
// Get corner radius from options
|
|
12738
|
-
const radius = this.cornerRadius();
|
|
12739
|
-
// Add bars with modern colors
|
|
12740
|
-
const bars = this.chart
|
|
12741
|
-
.selectAll('.axp-bar-chart-bar')
|
|
12742
|
-
.data(data)
|
|
12743
|
-
.enter()
|
|
12744
|
-
.append('rect')
|
|
12745
|
-
.attr('class', 'axp-bar-chart-bar')
|
|
12746
|
-
.attr('x', (d) => this.xScale(d.label))
|
|
12747
|
-
.attr('width', this.xScale.bandwidth())
|
|
12748
|
-
.attr('y', this.height) // Start from bottom for animation
|
|
12749
|
-
.attr('height', 0) // Start with height 0 for animation
|
|
12750
|
-
.attr('rx', radius) // Rounded corners
|
|
12751
|
-
.attr('ry', radius) // Rounded corners
|
|
12752
|
-
.attr('fill', (d, i) => d.color || AXPChartColors.getColor(i))
|
|
12753
|
-
.on('mouseenter', (event, d) => this.handleBarHover(event, d))
|
|
12754
|
-
.on('mousemove', (event) => this.updateTooltipPosition(event))
|
|
12755
|
-
.on('mouseleave', () => this._tooltipVisible.set(false))
|
|
12756
|
-
.on('click', (event, d) => this.handleBarClick(event, d));
|
|
12757
|
-
// Add animation
|
|
12758
|
-
bars
|
|
12759
|
-
.transition()
|
|
12760
|
-
.duration(800)
|
|
12761
|
-
.delay((d, i) => i * 50) // Stagger each bar animation
|
|
12762
|
-
.attr('y', (d) => this.yScale(d.value))
|
|
12763
|
-
.attr('height', (d) => this.height - this.yScale(d.value))
|
|
12764
|
-
.ease(this.d3.easeQuadOut); // Simple quadratic ease-out animation
|
|
12765
|
-
}
|
|
12766
|
-
// Event handlers
|
|
12767
|
-
/**
|
|
12768
|
-
* Handles bar hover event and shows tooltip
|
|
12769
|
-
*/
|
|
12770
|
-
handleBarHover(event, datum) {
|
|
12771
|
-
if (this.options().showTooltip !== false) {
|
|
12772
|
-
const index = this.chartData().findIndex((item) => item.id === datum.id);
|
|
12773
|
-
const color = datum.color || AXPChartColors.getColor(index);
|
|
12774
|
-
// Calculate percentage of total
|
|
12775
|
-
const total = this.chartData().reduce((sum, item) => sum + item.value, 0);
|
|
12776
|
-
const percentage = total > 0 ? ((datum.value / total) * 100).toFixed(1) : '0';
|
|
12777
|
-
this._tooltipData.set({
|
|
12778
|
-
title: datum.label,
|
|
12779
|
-
value: datum.value.toString(),
|
|
12780
|
-
percentage: `${percentage}%`,
|
|
12781
|
-
color: color,
|
|
12782
|
-
});
|
|
12783
|
-
this.updateTooltipPosition(event);
|
|
12784
|
-
this._tooltipVisible.set(true);
|
|
12785
|
-
}
|
|
12786
|
-
}
|
|
12787
|
-
/**
|
|
12788
|
-
* Updates tooltip position based on mouse coordinates
|
|
12789
|
-
*/
|
|
12790
|
-
updateTooltipPosition(event) {
|
|
12791
|
-
const container = this.chartContainerEl().nativeElement.getBoundingClientRect();
|
|
12792
|
-
const x = event.clientX - container.left;
|
|
12793
|
-
const y = event.clientY - container.top;
|
|
12794
|
-
this._tooltipPosition.set({
|
|
12795
|
-
x: x,
|
|
12796
|
-
y: y,
|
|
12797
|
-
});
|
|
12798
|
-
}
|
|
12799
|
-
/**
|
|
12800
|
-
* Handles bar click event
|
|
12801
|
-
*/
|
|
12802
|
-
handleBarClick(event, datum) {
|
|
12803
|
-
this.barClick.emit(datum);
|
|
12804
|
-
}
|
|
12805
|
-
/**
|
|
12806
|
-
* Shows a message when no data is available
|
|
12807
|
-
*/
|
|
12808
|
-
showNoDataMessage(containerElement) {
|
|
12809
|
-
const messageContainer = this.d3
|
|
12810
|
-
.select(containerElement)
|
|
12811
|
-
.append('div')
|
|
12812
|
-
.attr('class', 'axp-bar-chart-no-data-message')
|
|
12813
|
-
.style('width', '100%')
|
|
12814
|
-
.style('height', '100%')
|
|
12815
|
-
.style('display', 'flex')
|
|
12816
|
-
.style('flex-direction', 'column')
|
|
12817
|
-
.style('align-items', 'center')
|
|
12818
|
-
.style('justify-content', 'center')
|
|
12819
|
-
.style('text-align', 'center');
|
|
12820
|
-
// Add an icon
|
|
12821
|
-
messageContainer
|
|
12822
|
-
.append('div')
|
|
12823
|
-
.attr('class', 'axp-bar-chart-no-data-icon')
|
|
12824
|
-
.style('margin-bottom', '10px')
|
|
12825
|
-
.style('color', 'var(--ax-text-muted, #999)')
|
|
12826
|
-
.html('<i class="fa-light fa-chart-bar fa-2x"></i>');
|
|
12827
|
-
// Add text message
|
|
12828
|
-
messageContainer
|
|
12829
|
-
.append('div')
|
|
12830
|
-
.attr('class', 'axp-bar-chart-no-data-text')
|
|
12831
|
-
.style('font-size', '16px')
|
|
12832
|
-
.style('font-weight', '600')
|
|
12833
|
-
.style('color', 'var(--ax-text-color, #333)')
|
|
12834
|
-
.text('No data available');
|
|
12835
|
-
}
|
|
12836
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPBarChartWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
12837
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.0.3", type: AXPBarChartWidgetViewComponent, isStandalone: true, selector: "ng-component", outputs: { barClick: "barClick" }, viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"axp-bar-chart\" #chartContainer>\n <!-- Shared tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"true\"\n ></ax-chart-tooltip>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px}.axp-bar-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden}.axp-bar-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}.axp-bar-chart-bar{transition:all .3s cubic-bezier(.4,0,.2,1);cursor:pointer}.axp-bar-chart-bar:hover{filter:brightness(.9);transform:translateY(-3px)}.axp-bar-chart-axis-x path,.axp-bar-chart-axis-y path{stroke:var(--ax-border-color, #e0e0e0)}.axp-bar-chart-axis-x line,.axp-bar-chart-axis-y line,.axp-bar-chart-grid line{stroke:var(--ax-border-color, #e0e0e0);stroke-dasharray:2,2;stroke-opacity:.5}.axp-bar-chart-grid path{stroke-width:0}.axp-bar-chart-axis-x text,.axp-bar-chart-axis-y text{fill:var(--ax-text-muted, #666);font-size:clamp(8px,2vmin,12px)}.axp-bar-chart-no-data-message{font-family:var(--ax-font-family, system-ui, sans-serif);display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:1rem;width:100%;height:100%}.axp-bar-chart-no-data-icon{margin-bottom:.75rem;color:var(--ax-text-muted, #999)}.axp-bar-chart-no-data-text{font-weight:600;color:var(--ax-text-color, #333)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AXPChartTooltipComponent, selector: "ax-chart-tooltip", inputs: ["data", "position", "visible", "showPercentage", "style"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
12838
|
-
}
|
|
12839
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPBarChartWidgetViewComponent, decorators: [{
|
|
12840
|
-
type: Component,
|
|
12841
|
-
args: [{ standalone: true, imports: [CommonModule, AXPChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"axp-bar-chart\" #chartContainer>\n <!-- Shared tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"true\"\n ></ax-chart-tooltip>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px}.axp-bar-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden}.axp-bar-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}.axp-bar-chart-bar{transition:all .3s cubic-bezier(.4,0,.2,1);cursor:pointer}.axp-bar-chart-bar:hover{filter:brightness(.9);transform:translateY(-3px)}.axp-bar-chart-axis-x path,.axp-bar-chart-axis-y path{stroke:var(--ax-border-color, #e0e0e0)}.axp-bar-chart-axis-x line,.axp-bar-chart-axis-y line,.axp-bar-chart-grid line{stroke:var(--ax-border-color, #e0e0e0);stroke-dasharray:2,2;stroke-opacity:.5}.axp-bar-chart-grid path{stroke-width:0}.axp-bar-chart-axis-x text,.axp-bar-chart-axis-y text{fill:var(--ax-text-muted, #666);font-size:clamp(8px,2vmin,12px)}.axp-bar-chart-no-data-message{font-family:var(--ax-font-family, system-ui, sans-serif);display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:1rem;width:100%;height:100%}.axp-bar-chart-no-data-icon{margin-bottom:.75rem;color:var(--ax-text-muted, #999)}.axp-bar-chart-no-data-text{font-weight:600;color:var(--ax-text-color, #333)}\n"] }]
|
|
12842
|
-
}] });
|
|
12843
|
-
|
|
12844
|
-
var barChartWidget_component = /*#__PURE__*/Object.freeze({
|
|
12845
|
-
__proto__: null,
|
|
12846
|
-
AXPBarChartWidgetViewComponent: AXPBarChartWidgetViewComponent
|
|
12847
|
-
});
|
|
12848
|
-
|
|
12849
|
-
const AXP_WIDGETS_CHART_CATEGORY = {
|
|
12850
|
-
name: 'chart',
|
|
12851
|
-
order: 6,
|
|
12852
|
-
title: 'Charts',
|
|
12853
|
-
};
|
|
12854
|
-
const AXP_WIDGETS_UTILITY_CATEGORY = {
|
|
12855
|
-
name: 'utility',
|
|
12856
|
-
order: 7,
|
|
12857
|
-
title: 'Utilities',
|
|
12858
|
-
};
|
|
12859
|
-
|
|
12860
|
-
const AXPBarChartWidget = {
|
|
12861
|
-
name: 'bar-chart',
|
|
12862
|
-
title: 'Bar Chart Widget',
|
|
12863
|
-
categories: [AXP_WIDGETS_CHART_CATEGORY],
|
|
12864
|
-
groups: [AXPWidgetGroupEnum.DashboardWidget],
|
|
12865
|
-
type: 'dashboard',
|
|
12866
|
-
icon: 'fa-light fa-chart-bar',
|
|
12867
|
-
properties: [
|
|
12868
|
-
// ====== Layout & Dimensions ======
|
|
12869
|
-
{
|
|
12870
|
-
name: 'height',
|
|
12871
|
-
title: 'Height',
|
|
12872
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
12873
|
-
schema: {
|
|
12874
|
-
defaultValue: 300,
|
|
12875
|
-
dataType: 'number',
|
|
12876
|
-
interface: {
|
|
12877
|
-
name: 'height',
|
|
12878
|
-
path: 'options.height',
|
|
12879
|
-
type: AXPWidgetsCatalog.number,
|
|
12880
|
-
options: {
|
|
12881
|
-
minValue: 0,
|
|
12882
|
-
maxValue: 800,
|
|
12883
|
-
},
|
|
12884
|
-
},
|
|
12885
|
-
},
|
|
12886
|
-
visible: true,
|
|
12887
|
-
},
|
|
12888
|
-
{
|
|
12889
|
-
name: 'width',
|
|
12890
|
-
title: 'Width',
|
|
12891
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
12892
|
-
schema: {
|
|
12893
|
-
defaultValue: null,
|
|
12894
|
-
dataType: 'number',
|
|
12895
|
-
interface: {
|
|
12896
|
-
name: 'width',
|
|
12897
|
-
path: 'options.width',
|
|
12898
|
-
type: AXPWidgetsCatalog.number,
|
|
12899
|
-
options: {
|
|
12900
|
-
minValue: 0,
|
|
12901
|
-
maxValue: 1200,
|
|
12902
|
-
},
|
|
12903
|
-
},
|
|
12904
|
-
},
|
|
12905
|
-
visible: true,
|
|
12906
|
-
},
|
|
12907
|
-
// ====== Axis Settings ======
|
|
12908
|
-
{
|
|
12909
|
-
name: 'showXAxis',
|
|
12910
|
-
title: 'Show X Axis',
|
|
12911
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
12912
|
-
schema: {
|
|
12913
|
-
defaultValue: true,
|
|
12914
|
-
dataType: 'boolean',
|
|
12915
|
-
interface: {
|
|
12916
|
-
name: 'showXAxis',
|
|
12917
|
-
path: 'options.showXAxis',
|
|
12918
|
-
type: AXPWidgetsCatalog.toggle,
|
|
12919
|
-
},
|
|
12920
|
-
},
|
|
12921
|
-
visible: true,
|
|
12922
|
-
},
|
|
12923
|
-
{
|
|
12924
|
-
name: 'showYAxis',
|
|
12925
|
-
title: 'Show Y Axis',
|
|
12926
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
12927
|
-
schema: {
|
|
12928
|
-
defaultValue: true,
|
|
12929
|
-
dataType: 'boolean',
|
|
12930
|
-
interface: {
|
|
12931
|
-
name: 'showYAxis',
|
|
12932
|
-
path: 'options.showYAxis',
|
|
12933
|
-
type: AXPWidgetsCatalog.toggle,
|
|
12934
|
-
},
|
|
12935
|
-
},
|
|
12936
|
-
visible: true,
|
|
12937
|
-
},
|
|
12938
|
-
{
|
|
12939
|
-
name: 'showGrid',
|
|
12940
|
-
title: 'Show Grid Lines',
|
|
12941
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
12942
|
-
schema: {
|
|
12943
|
-
defaultValue: true,
|
|
12944
|
-
dataType: 'boolean',
|
|
12945
|
-
interface: {
|
|
12946
|
-
name: 'showGrid',
|
|
12947
|
-
path: 'options.showGrid',
|
|
12948
|
-
type: AXPWidgetsCatalog.toggle,
|
|
12949
|
-
},
|
|
12950
|
-
},
|
|
12951
|
-
visible: true,
|
|
12952
|
-
},
|
|
12953
|
-
// ====== Tooltip Settings ======
|
|
12954
|
-
{
|
|
12955
|
-
name: 'showTooltip',
|
|
12956
|
-
title: 'Show Tooltip',
|
|
12957
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
12958
|
-
schema: {
|
|
12959
|
-
defaultValue: true,
|
|
12960
|
-
dataType: 'boolean',
|
|
12961
|
-
interface: {
|
|
12962
|
-
name: 'showTooltip',
|
|
12963
|
-
path: 'options.showTooltip',
|
|
12964
|
-
type: AXPWidgetsCatalog.toggle,
|
|
12965
|
-
},
|
|
12966
|
-
},
|
|
12967
|
-
visible: true,
|
|
12968
|
-
},
|
|
12969
|
-
// ====== Bar Appearance ======
|
|
12970
|
-
{
|
|
12971
|
-
name: 'barWidth',
|
|
12972
|
-
title: 'Bar Width',
|
|
12973
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
12974
|
-
schema: {
|
|
12975
|
-
defaultValue: 80,
|
|
12976
|
-
dataType: 'number',
|
|
12977
|
-
interface: {
|
|
12978
|
-
name: 'barWidth',
|
|
12979
|
-
path: 'options.barWidth',
|
|
12980
|
-
type: AXPWidgetsCatalog.number,
|
|
12981
|
-
options: {
|
|
12982
|
-
placeholder: '1-100',
|
|
12983
|
-
minValue: 1,
|
|
12984
|
-
maxValue: 100,
|
|
12985
|
-
},
|
|
12986
|
-
},
|
|
12987
|
-
},
|
|
12988
|
-
visible: true,
|
|
12989
|
-
},
|
|
12990
|
-
{
|
|
12991
|
-
name: 'cornerRadius',
|
|
12992
|
-
title: 'Corner Radius',
|
|
12993
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
12994
|
-
schema: {
|
|
12995
|
-
defaultValue: 4,
|
|
12996
|
-
dataType: 'number',
|
|
12997
|
-
interface: {
|
|
12998
|
-
name: 'cornerRadius',
|
|
12999
|
-
path: 'options.cornerRadius',
|
|
13000
|
-
type: AXPWidgetsCatalog.number,
|
|
13001
|
-
options: {
|
|
13002
|
-
placeholder: '0-20',
|
|
13003
|
-
minValue: 0,
|
|
13004
|
-
maxValue: 20,
|
|
13005
|
-
},
|
|
13006
|
-
},
|
|
13007
|
-
},
|
|
13008
|
-
visible: true,
|
|
13009
|
-
},
|
|
13010
|
-
],
|
|
13011
|
-
components: {
|
|
13012
|
-
view: {
|
|
13013
|
-
component: () => Promise.resolve().then(function () { return barChartWidget_component; }).then((c) => c.AXPBarChartWidgetViewComponent),
|
|
13014
|
-
},
|
|
13015
|
-
},
|
|
13016
|
-
meta: {
|
|
13017
|
-
dimensions: {
|
|
13018
|
-
width: 5,
|
|
13019
|
-
height: 6,
|
|
13020
|
-
minWidth: 2,
|
|
13021
|
-
minHeight: 2,
|
|
13022
|
-
maxWidth: 6,
|
|
13023
|
-
maxHeight: 7,
|
|
13024
|
-
},
|
|
13025
|
-
},
|
|
13026
|
-
};
|
|
13027
|
-
|
|
13028
|
-
class AXPClockCalendarWidgetViewComponent extends AXPValueWidgetComponent {
|
|
13029
|
-
constructor() {
|
|
13030
|
-
super(...arguments);
|
|
13031
|
-
// Dependencies
|
|
13032
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
13033
|
-
// Time state
|
|
13034
|
-
this.currentTime = new Date();
|
|
13035
|
-
this.currentDate = new Date();
|
|
13036
|
-
this.clockSubscription = null;
|
|
13037
|
-
// Static clock elements
|
|
13038
|
-
this.clockHours = Array.from({ length: 12 }, (_, i) => i + 1);
|
|
13039
|
-
this.clockHourNumbers = Array.from({ length: 12 }, (_, i) => ({
|
|
13040
|
-
number: i === 0 ? 12 : i,
|
|
13041
|
-
angle: i * 30,
|
|
13042
|
-
}));
|
|
13043
|
-
// Clock hands rotation angles
|
|
13044
|
-
this.hourRotation = 0;
|
|
13045
|
-
this.minuteRotation = 0;
|
|
13046
|
-
this.secondRotation = 0;
|
|
13047
|
-
// Options with computed properties and defaults
|
|
13048
|
-
this.displayLayout = computed(() => this.options()?.displayLayout?.id ?? 'both');
|
|
13049
|
-
this.showDigitalClock = computed(() => {
|
|
13050
|
-
const layout = this.displayLayout();
|
|
13051
|
-
return layout === 'both' || layout === 'digital';
|
|
13052
|
-
});
|
|
13053
|
-
this.showAnalogClock = computed(() => {
|
|
13054
|
-
const layout = this.displayLayout();
|
|
13055
|
-
return layout === 'both' || layout === 'analog';
|
|
13056
|
-
});
|
|
13057
|
-
this.showDate = computed(() => this.options()?.showDate !== false);
|
|
13058
|
-
this.showDayOfWeek = computed(() => this.options()?.showDayOfWeek !== false);
|
|
13059
|
-
this.use24Hour = computed(() => this.options()?.use24Hour === true);
|
|
13060
|
-
this.showSeconds = computed(() => this.options()?.showSeconds !== false);
|
|
13061
|
-
this.dateFormat = computed(() => this.options()?.dateFormat?.id ?? 'dd MMM yyyy');
|
|
13062
|
-
this.timezone = computed(() => this.options()?.timezone?.id ?? 'local');
|
|
13063
|
-
this.showTimezoneIndicator = computed(() => this.timezone() !== 'local');
|
|
13064
|
-
this.displayTimezone = computed(() => {
|
|
13065
|
-
const tz = this.timezone();
|
|
13066
|
-
if (tz === 'local')
|
|
13067
|
-
return '';
|
|
13068
|
-
if (tz.startsWith('UTC')) {
|
|
13069
|
-
const [match, sign, hours, minutes] = tz.match(/UTC([+-])(\d+):?(\d+)?/) ?? [];
|
|
13070
|
-
if (match) {
|
|
13071
|
-
if (!minutes || minutes === '00') {
|
|
13072
|
-
return `UTC${sign}${parseInt(hours)}`;
|
|
13073
|
-
}
|
|
13074
|
-
return `UTC${sign}${parseInt(hours)}:${minutes}`;
|
|
13075
|
-
}
|
|
13076
|
-
}
|
|
13077
|
-
return tz;
|
|
13078
|
-
});
|
|
13079
|
-
this.timeFormat = computed(() => this.showSeconds() ? (this.use24Hour() ? 'HH:mm:ss' : 'hh:mm:ss a') : this.use24Hour() ? 'HH:mm' : 'hh:mm a');
|
|
13080
|
-
}
|
|
13081
|
-
ngOnInit() {
|
|
13082
|
-
super.ngOnInit();
|
|
13083
|
-
this.startClockTimer();
|
|
13084
|
-
this.updateTime();
|
|
13085
|
-
}
|
|
13086
|
-
ngOnDestroy() {
|
|
13087
|
-
this.stopClockTimer();
|
|
13088
|
-
}
|
|
13089
|
-
startClockTimer() {
|
|
13090
|
-
this.stopClockTimer();
|
|
13091
|
-
this.clockSubscription = interval(1000).subscribe(() => this.updateTime());
|
|
13092
|
-
}
|
|
13093
|
-
stopClockTimer() {
|
|
13094
|
-
this.clockSubscription?.unsubscribe();
|
|
13095
|
-
this.clockSubscription = null;
|
|
13096
|
-
}
|
|
13097
|
-
updateTime() {
|
|
13098
|
-
const now = this.getAdjustedTime();
|
|
13099
|
-
this.currentTime = now;
|
|
13100
|
-
this.currentDate = now;
|
|
13101
|
-
this.updateClockHandsRotation(now);
|
|
13102
|
-
this.cdr.markForCheck();
|
|
13103
|
-
}
|
|
13104
|
-
getAdjustedTime() {
|
|
13105
|
-
const now = new Date();
|
|
13106
|
-
const tz = this.timezone();
|
|
13107
|
-
if (tz === 'local' || !tz.startsWith('UTC')) {
|
|
13108
|
-
return now;
|
|
13109
|
-
}
|
|
13110
|
-
const [match, sign, hours, minutes] = tz.match(/UTC([+-])(\d+):?(\d+)?/) ?? [];
|
|
13111
|
-
if (!match) {
|
|
13112
|
-
return now;
|
|
13113
|
-
}
|
|
13114
|
-
const offsetHours = parseInt(hours) * (sign === '+' ? 1 : -1);
|
|
13115
|
-
const offsetMinutes = minutes ? parseInt(minutes) * (sign === '+' ? 1 : -1) : 0;
|
|
13116
|
-
const utcTime = now.getTime() + now.getTimezoneOffset() * 60000;
|
|
13117
|
-
return new Date(utcTime + (offsetHours * 3600000 + offsetMinutes * 60000));
|
|
13118
|
-
}
|
|
13119
|
-
updateClockHandsRotation(now) {
|
|
13120
|
-
const hours = now.getHours() % 12;
|
|
13121
|
-
const minutes = now.getMinutes();
|
|
13122
|
-
const seconds = now.getSeconds();
|
|
13123
|
-
// For analog clocks:
|
|
13124
|
-
// - 12 o'clock is 0°
|
|
13125
|
-
// - 3 o'clock is 90°
|
|
13126
|
-
// - 6 o'clock is 180°
|
|
13127
|
-
// - 9 o'clock is 270°
|
|
13128
|
-
// Hour hand: 30° per hour (360°/12) plus gradual movement from minutes
|
|
13129
|
-
this.hourRotation = hours * 30 + minutes / 2 + 180;
|
|
13130
|
-
// Minute hand: 6° per minute (360°/60)
|
|
13131
|
-
this.minuteRotation = minutes * 6 + 180;
|
|
13132
|
-
// Second hand: 6° per second (360°/60)
|
|
13133
|
-
this.secondRotation = seconds * 6 + 180;
|
|
13134
|
-
}
|
|
13135
|
-
initializeWidget() {
|
|
13136
|
-
this.updateTime();
|
|
13137
|
-
}
|
|
13138
|
-
updateWidget() {
|
|
13139
|
-
this.stopClockTimer();
|
|
13140
|
-
this.startClockTimer();
|
|
13141
|
-
this.updateTime();
|
|
13142
|
-
}
|
|
13143
|
-
destroyWidget() {
|
|
13144
|
-
this.stopClockTimer();
|
|
13145
|
-
}
|
|
13146
|
-
getDayOfWeek() {
|
|
13147
|
-
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
13148
|
-
return days[this.currentDate.getDay()];
|
|
13149
|
-
}
|
|
13150
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPClockCalendarWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
13151
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: AXPClockCalendarWidgetViewComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: "<div class=\"axp-clock-calendar-container\">\n <!-- Timezone indicator (only shown when not local) -->\n @if (showTimezoneIndicator()) {\n <div class=\"axp-clock-calendar-timezone-badge\"><i class=\"fa-solid fa-globe\"></i> {{ displayTimezone() }}</div>\n }\n\n <!-- Day of week display -->\n @if (showDayOfWeek()) {\n <div class=\"axp-clock-calendar-day-label\">{{ getDayOfWeek() }}</div>\n }\n\n <div class=\"axp-clock-calendar-content\">\n <!-- Digital Clock Display -->\n @if (showDigitalClock()) {\n <div class=\"axp-clock-calendar-digital-clock\">\n {{ currentTime | format : 'datetime' : timeFormat() | async }}\n </div>\n }\n\n <!-- Analog Clock Display -->\n @if (showAnalogClock()) {\n <div class=\"axp-clock-calendar-analog-clock\">\n <!-- Hour markers -->\n @for (hour of clockHours; track hour) {\n <div\n class=\"axp-clock-calendar-hour-marker\"\n [style.transform]=\"'rotate(' + hour * 30 + 'deg) translateY(-80px)'\"\n ></div>\n }\n\n <!-- Clock Numbers -->\n <div class=\"axp-clock-calendar-numbers-container\">\n @for (hour of clockHourNumbers; track hour) {\n <div\n class=\"axp-clock-calendar-hour-number\"\n [style.transform]=\"'rotate(' + hour.angle + 'deg) translateY(-82px)'\"\n >\n <span [style.transform]=\"'rotate(' + -hour.angle + 'deg)'\">{{ hour.number }}</span>\n </div>\n }\n </div>\n\n <!-- Clock Hands -->\n <div class=\"axp-clock-calendar-hands-container\">\n <div class=\"axp-clock-calendar-hour-hand\" [style.transform]=\"'rotate(' + hourRotation + 'deg)'\"></div>\n <div class=\"axp-clock-calendar-minute-hand\" [style.transform]=\"'rotate(' + minuteRotation + 'deg)'\"></div>\n @if (showSeconds()) {\n <div class=\"axp-clock-calendar-second-hand\" [style.transform]=\"'rotate(' + secondRotation + 'deg)'\"></div>\n }\n <div class=\"axp-clock-calendar-center-dot\"></div>\n </div>\n </div>\n }\n\n <!-- Date Display -->\n @if (showDate()) {\n <div class=\"axp-clock-calendar-date-display\">\n <i class=\"fa-regular fa-calendar\"></i>\n {{ currentDate | format : 'datetime' : dateFormat() | async }}\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.axp-clock-calendar-container{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%;padding:1rem;position:relative;overflow:hidden;box-shadow:var(--ax-shadow-sm);background-color:var(--ax-surface-color);color:var(--ax-text-color)}.axp-clock-calendar-content{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;width:100%;height:100%;position:relative}.axp-clock-calendar-timezone-badge{position:absolute;top:.5rem;right:.5rem;font-size:.75rem;padding:.25rem;border-radius:1rem;border-width:1px;border-color:rgba(23,23,23,.5)}.axp-clock-calendar-timezone-badge:is(.ax-dark *){border-color:rgba(245,245,245,.5)}.axp-clock-calendar-timezone-badge{display:flex;align-items:center;gap:.25rem}.axp-clock-calendar-timezone-badge i{font-size:.75rem}.axp-clock-calendar-day-label{font-size:.9rem;font-weight:600;text-transform:uppercase;letter-spacing:.05rem;margin-bottom:.25rem}.axp-clock-calendar-digital-clock{font-size:1.5rem;font-weight:500;letter-spacing:.05rem;font-family:monospace;padding:.5rem .75rem;border-radius:.25rem}.axp-clock-calendar-analog-clock{position:relative;border-radius:50%;border-width:1px;border-color:rgba(23,23,23,.5)}.axp-clock-calendar-analog-clock:is(.ax-dark *){border-color:rgba(245,245,245,.5)}.axp-clock-calendar-analog-clock{display:flex;align-items:center;justify-content:center;background:var(--ax-surface-color);width:min(180px,100%);height:0;padding-bottom:min(180px,100%);margin:.5rem auto;min-width:120px;min-height:120px;box-shadow:var(--ax-shadow-sm)}.axp-clock-calendar-hands-container,.axp-clock-calendar-numbers-container{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%}.axp-clock-calendar-hour-marker{position:absolute;left:50%;top:50%;width:2px;height:4%;margin-left:-1px;border-radius:1px;background-color:var(--ax-text-muted);transform-origin:50% 0}.axp-clock-calendar-hour-number{position:absolute;left:50%;top:50%;transform-origin:50% 0;font-size:clamp(.7rem,2.5vw,.9rem);font-weight:600;color:var(--ax-text-color)}.axp-clock-calendar-hour-number span{display:block}.axp-clock-calendar-hour-hand{position:absolute;top:50%;left:50%;width:4px;height:25%;margin-left:-2px;background-color:rgba(23,23,23,.75)}.axp-clock-calendar-hour-hand:is(.ax-dark *){background-color:rgba(245,245,245,.75)}.axp-clock-calendar-hour-hand{transform-origin:50% 0;border-radius:3px;box-shadow:0 0 4px rgba(0,0,0,.3)}.axp-clock-calendar-minute-hand{position:absolute;top:50%;left:50%;width:3px;height:38%;margin-left:-1.5px;background-color:rgba(23,23,23,.5)}.axp-clock-calendar-minute-hand:is(.ax-dark *){background-color:rgba(245,245,245,.5)}.axp-clock-calendar-minute-hand{transform-origin:50% 0;border-radius:2px;box-shadow:0 0 4px rgba(0,0,0,.3)}.axp-clock-calendar-second-hand{position:absolute;top:50%;left:50%;width:2px;height:42%;margin-left:-1px;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-500),var(--tw-bg-opacity, 1))}.axp-clock-calendar-second-hand:is(.ax-dark *){--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-400),var(--tw-bg-opacity, 1))}.axp-clock-calendar-second-hand{transform-origin:50% 0;border-radius:1px;box-shadow:0 0 3px rgba(0,0,0,.2)}.axp-clock-calendar-center-dot{position:absolute;top:50%;left:50%;width:10px;height:10px;border-radius:50%;margin-top:-5px;margin-left:-5px;background-color:#d81159;border:2px solid var(--ax-surface-color);box-shadow:0 0 4px rgba(0,0,0,.3)}.axp-clock-calendar-date-display{display:flex;align-items:center;gap:.5rem;font-size:.85rem;padding:.25rem .6rem;border-radius:.25rem;background-color:var(--ax-surface-hover);color:var(--ax-text-color)}.axp-clock-calendar-date-display i{font-size:.85rem;opacity:.7}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: AXDateTimeModule }, { kind: "ngmodule", type: AXFormatModule }, { kind: "pipe", type: i2$9.AXFormatPipe, name: "format" }, { kind: "ngmodule", type: AXTagModule }, { kind: "ngmodule", type: AXDecoratorModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
13152
|
-
}
|
|
13153
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPClockCalendarWidgetViewComponent, decorators: [{
|
|
13154
|
-
type: Component,
|
|
13155
|
-
args: [{ standalone: true, imports: [CommonModule, AXDateTimeModule, AXFormatModule, AXTagModule, AXDecoratorModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"axp-clock-calendar-container\">\n <!-- Timezone indicator (only shown when not local) -->\n @if (showTimezoneIndicator()) {\n <div class=\"axp-clock-calendar-timezone-badge\"><i class=\"fa-solid fa-globe\"></i> {{ displayTimezone() }}</div>\n }\n\n <!-- Day of week display -->\n @if (showDayOfWeek()) {\n <div class=\"axp-clock-calendar-day-label\">{{ getDayOfWeek() }}</div>\n }\n\n <div class=\"axp-clock-calendar-content\">\n <!-- Digital Clock Display -->\n @if (showDigitalClock()) {\n <div class=\"axp-clock-calendar-digital-clock\">\n {{ currentTime | format : 'datetime' : timeFormat() | async }}\n </div>\n }\n\n <!-- Analog Clock Display -->\n @if (showAnalogClock()) {\n <div class=\"axp-clock-calendar-analog-clock\">\n <!-- Hour markers -->\n @for (hour of clockHours; track hour) {\n <div\n class=\"axp-clock-calendar-hour-marker\"\n [style.transform]=\"'rotate(' + hour * 30 + 'deg) translateY(-80px)'\"\n ></div>\n }\n\n <!-- Clock Numbers -->\n <div class=\"axp-clock-calendar-numbers-container\">\n @for (hour of clockHourNumbers; track hour) {\n <div\n class=\"axp-clock-calendar-hour-number\"\n [style.transform]=\"'rotate(' + hour.angle + 'deg) translateY(-82px)'\"\n >\n <span [style.transform]=\"'rotate(' + -hour.angle + 'deg)'\">{{ hour.number }}</span>\n </div>\n }\n </div>\n\n <!-- Clock Hands -->\n <div class=\"axp-clock-calendar-hands-container\">\n <div class=\"axp-clock-calendar-hour-hand\" [style.transform]=\"'rotate(' + hourRotation + 'deg)'\"></div>\n <div class=\"axp-clock-calendar-minute-hand\" [style.transform]=\"'rotate(' + minuteRotation + 'deg)'\"></div>\n @if (showSeconds()) {\n <div class=\"axp-clock-calendar-second-hand\" [style.transform]=\"'rotate(' + secondRotation + 'deg)'\"></div>\n }\n <div class=\"axp-clock-calendar-center-dot\"></div>\n </div>\n </div>\n }\n\n <!-- Date Display -->\n @if (showDate()) {\n <div class=\"axp-clock-calendar-date-display\">\n <i class=\"fa-regular fa-calendar\"></i>\n {{ currentDate | format : 'datetime' : dateFormat() | async }}\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.axp-clock-calendar-container{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%;padding:1rem;position:relative;overflow:hidden;box-shadow:var(--ax-shadow-sm);background-color:var(--ax-surface-color);color:var(--ax-text-color)}.axp-clock-calendar-content{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;width:100%;height:100%;position:relative}.axp-clock-calendar-timezone-badge{position:absolute;top:.5rem;right:.5rem;font-size:.75rem;padding:.25rem;border-radius:1rem;border-width:1px;border-color:rgba(23,23,23,.5)}.axp-clock-calendar-timezone-badge:is(.ax-dark *){border-color:rgba(245,245,245,.5)}.axp-clock-calendar-timezone-badge{display:flex;align-items:center;gap:.25rem}.axp-clock-calendar-timezone-badge i{font-size:.75rem}.axp-clock-calendar-day-label{font-size:.9rem;font-weight:600;text-transform:uppercase;letter-spacing:.05rem;margin-bottom:.25rem}.axp-clock-calendar-digital-clock{font-size:1.5rem;font-weight:500;letter-spacing:.05rem;font-family:monospace;padding:.5rem .75rem;border-radius:.25rem}.axp-clock-calendar-analog-clock{position:relative;border-radius:50%;border-width:1px;border-color:rgba(23,23,23,.5)}.axp-clock-calendar-analog-clock:is(.ax-dark *){border-color:rgba(245,245,245,.5)}.axp-clock-calendar-analog-clock{display:flex;align-items:center;justify-content:center;background:var(--ax-surface-color);width:min(180px,100%);height:0;padding-bottom:min(180px,100%);margin:.5rem auto;min-width:120px;min-height:120px;box-shadow:var(--ax-shadow-sm)}.axp-clock-calendar-hands-container,.axp-clock-calendar-numbers-container{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%}.axp-clock-calendar-hour-marker{position:absolute;left:50%;top:50%;width:2px;height:4%;margin-left:-1px;border-radius:1px;background-color:var(--ax-text-muted);transform-origin:50% 0}.axp-clock-calendar-hour-number{position:absolute;left:50%;top:50%;transform-origin:50% 0;font-size:clamp(.7rem,2.5vw,.9rem);font-weight:600;color:var(--ax-text-color)}.axp-clock-calendar-hour-number span{display:block}.axp-clock-calendar-hour-hand{position:absolute;top:50%;left:50%;width:4px;height:25%;margin-left:-2px;background-color:rgba(23,23,23,.75)}.axp-clock-calendar-hour-hand:is(.ax-dark *){background-color:rgba(245,245,245,.75)}.axp-clock-calendar-hour-hand{transform-origin:50% 0;border-radius:3px;box-shadow:0 0 4px rgba(0,0,0,.3)}.axp-clock-calendar-minute-hand{position:absolute;top:50%;left:50%;width:3px;height:38%;margin-left:-1.5px;background-color:rgba(23,23,23,.5)}.axp-clock-calendar-minute-hand:is(.ax-dark *){background-color:rgba(245,245,245,.5)}.axp-clock-calendar-minute-hand{transform-origin:50% 0;border-radius:2px;box-shadow:0 0 4px rgba(0,0,0,.3)}.axp-clock-calendar-second-hand{position:absolute;top:50%;left:50%;width:2px;height:42%;margin-left:-1px;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-500),var(--tw-bg-opacity, 1))}.axp-clock-calendar-second-hand:is(.ax-dark *){--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-danger-400),var(--tw-bg-opacity, 1))}.axp-clock-calendar-second-hand{transform-origin:50% 0;border-radius:1px;box-shadow:0 0 3px rgba(0,0,0,.2)}.axp-clock-calendar-center-dot{position:absolute;top:50%;left:50%;width:10px;height:10px;border-radius:50%;margin-top:-5px;margin-left:-5px;background-color:#d81159;border:2px solid var(--ax-surface-color);box-shadow:0 0 4px rgba(0,0,0,.3)}.axp-clock-calendar-date-display{display:flex;align-items:center;gap:.5rem;font-size:.85rem;padding:.25rem .6rem;border-radius:.25rem;background-color:var(--ax-surface-hover);color:var(--ax-text-color)}.axp-clock-calendar-date-display i{font-size:.85rem;opacity:.7}\n"] }]
|
|
13156
|
-
}] });
|
|
13157
|
-
|
|
13158
|
-
var clockCalendarWidget_component = /*#__PURE__*/Object.freeze({
|
|
13159
|
-
__proto__: null,
|
|
13160
|
-
AXPClockCalendarWidgetViewComponent: AXPClockCalendarWidgetViewComponent
|
|
13161
|
-
});
|
|
13162
|
-
|
|
13163
|
-
/**
|
|
13164
|
-
* ACoreX Clock Calendar Widget Types
|
|
13165
|
-
* Contains all types and interfaces for the Clock Calendar widget
|
|
13166
|
-
*/
|
|
13167
|
-
/**
|
|
13168
|
-
* Common timezone options for the widget configuration
|
|
13169
|
-
*/
|
|
13170
|
-
const AXP_TIMEZONE_OPTIONS = [
|
|
13171
|
-
{ id: 'local', title: 'Local Time' },
|
|
13172
|
-
{ id: 'UTC+0', title: 'UTC (GMT)' },
|
|
13173
|
-
{ id: 'UTC+1', title: 'Central European Time (UTC+1)' },
|
|
13174
|
-
{ id: 'UTC+2', title: 'Eastern European Time (UTC+2)' },
|
|
13175
|
-
{ id: 'UTC+3', title: 'Moscow Time (UTC+3)' },
|
|
13176
|
-
{ id: 'UTC+4', title: 'Gulf Time (UTC+4)' },
|
|
13177
|
-
{ id: 'UTC+5:30', title: 'Indian Time (UTC+5:30)' },
|
|
13178
|
-
{ id: 'UTC+8', title: 'China Time (UTC+8)' },
|
|
13179
|
-
{ id: 'UTC+9', title: 'Japan Time (UTC+9)' },
|
|
13180
|
-
{ id: 'UTC+10', title: 'Australian Eastern Time (UTC+10)' },
|
|
13181
|
-
{ id: 'UTC-5', title: 'Eastern Time (UTC-5)' },
|
|
13182
|
-
{ id: 'UTC-6', title: 'Central Time (UTC-6)' },
|
|
13183
|
-
{ id: 'UTC-7', title: 'Mountain Time (UTC-7)' },
|
|
13184
|
-
{ id: 'UTC-8', title: 'Pacific Time (UTC-8)' },
|
|
13185
|
-
];
|
|
13186
|
-
/**
|
|
13187
|
-
* Common date format options for the widget configuration
|
|
13188
|
-
*/
|
|
13189
|
-
const AXP_DATE_FORMAT_OPTIONS = [
|
|
13190
|
-
{ id: 'dd MMM yyyy', title: '31 Dec 2023' },
|
|
13191
|
-
{ id: 'MMM dd, yyyy', title: 'Dec 31, 2023' },
|
|
13192
|
-
{ id: 'dd/MM/yyyy', title: '31/12/2023' },
|
|
13193
|
-
{ id: 'MM/dd/yyyy', title: '12/31/2023' },
|
|
13194
|
-
];
|
|
13195
|
-
|
|
13196
|
-
const AXPClockCalendarWidget = {
|
|
13197
|
-
name: 'clock-calendar',
|
|
13198
|
-
title: 'Clock & Calendar',
|
|
13199
|
-
categories: [AXP_WIDGETS_UTILITY_CATEGORY],
|
|
13200
|
-
groups: [AXPWidgetGroupEnum.DashboardWidget],
|
|
13201
|
-
type: 'dashboard',
|
|
13202
|
-
icon: 'fa-light fa-clock',
|
|
13203
|
-
properties: [
|
|
13204
|
-
// ====== Display Settings ======
|
|
13205
|
-
{
|
|
13206
|
-
name: 'displayLayout',
|
|
13207
|
-
title: 'Display Layout',
|
|
13208
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13209
|
-
schema: {
|
|
13210
|
-
dataType: 'string',
|
|
13211
|
-
defaultValue: 'both',
|
|
13212
|
-
interface: {
|
|
13213
|
-
name: 'displayLayout',
|
|
13214
|
-
path: 'options.displayLayout',
|
|
13215
|
-
type: AXPWidgetsCatalog.select,
|
|
13216
|
-
options: {
|
|
13217
|
-
dataSource: [
|
|
13218
|
-
{ id: 'both', title: 'Digital & Analog' },
|
|
13219
|
-
{ id: 'digital', title: 'Digital Only' },
|
|
13220
|
-
{ id: 'analog', title: 'Analog Only' },
|
|
13221
|
-
],
|
|
13222
|
-
},
|
|
13223
|
-
},
|
|
13224
|
-
},
|
|
13225
|
-
visible: true,
|
|
13226
|
-
},
|
|
13227
|
-
{
|
|
13228
|
-
name: 'showDate',
|
|
13229
|
-
title: 'Show Date',
|
|
13230
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13231
|
-
schema: {
|
|
13232
|
-
defaultValue: true,
|
|
13233
|
-
dataType: 'boolean',
|
|
13234
|
-
interface: {
|
|
13235
|
-
name: 'showDate',
|
|
13236
|
-
path: 'options.showDate',
|
|
13237
|
-
type: AXPWidgetsCatalog.toggle,
|
|
13238
|
-
},
|
|
13239
|
-
},
|
|
13240
|
-
visible: true,
|
|
13241
|
-
},
|
|
13242
|
-
{
|
|
13243
|
-
name: 'showDayOfWeek',
|
|
13244
|
-
title: 'Show Day of Week',
|
|
13245
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13246
|
-
schema: {
|
|
13247
|
-
defaultValue: true,
|
|
13248
|
-
dataType: 'boolean',
|
|
13249
|
-
interface: {
|
|
13250
|
-
name: 'showDayOfWeek',
|
|
13251
|
-
path: 'options.showDayOfWeek',
|
|
13252
|
-
type: AXPWidgetsCatalog.toggle,
|
|
13253
|
-
},
|
|
13254
|
-
},
|
|
13255
|
-
visible: true,
|
|
13256
|
-
},
|
|
13257
|
-
// ====== Time Format Settings ======
|
|
13258
|
-
{
|
|
13259
|
-
name: 'use24Hour',
|
|
13260
|
-
title: 'Use 24 Hour Format',
|
|
13261
|
-
group: AXP_BEHAVIOR_PROPERTY_GROUP,
|
|
13262
|
-
schema: {
|
|
13263
|
-
defaultValue: false,
|
|
13264
|
-
dataType: 'boolean',
|
|
13265
|
-
interface: {
|
|
13266
|
-
name: 'use24Hour',
|
|
13267
|
-
path: 'options.use24Hour',
|
|
13268
|
-
type: AXPWidgetsCatalog.toggle,
|
|
13269
|
-
},
|
|
13270
|
-
},
|
|
13271
|
-
visible: true,
|
|
13272
|
-
},
|
|
13273
|
-
{
|
|
13274
|
-
name: 'showSeconds',
|
|
13275
|
-
title: 'Show Seconds',
|
|
13276
|
-
group: AXP_BEHAVIOR_PROPERTY_GROUP,
|
|
13277
|
-
schema: {
|
|
13278
|
-
defaultValue: true,
|
|
13279
|
-
dataType: 'boolean',
|
|
13280
|
-
interface: {
|
|
13281
|
-
name: 'showSeconds',
|
|
13282
|
-
path: 'options.showSeconds',
|
|
13283
|
-
type: AXPWidgetsCatalog.toggle,
|
|
13284
|
-
},
|
|
13285
|
-
},
|
|
13286
|
-
visible: true,
|
|
13287
|
-
},
|
|
13288
|
-
// {
|
|
13289
|
-
// name: 'showHourMarkers',
|
|
13290
|
-
// title: 'Show Hour Markers',
|
|
13291
|
-
// group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13292
|
-
// schema: {
|
|
13293
|
-
// defaultValue: true,
|
|
13294
|
-
// dataType: 'boolean',
|
|
13295
|
-
// interface: {
|
|
13296
|
-
// name: 'showHourMarkers',
|
|
13297
|
-
// path: 'options.showHourMarkers',
|
|
13298
|
-
// type: AXPWidgetsCatalog.toggle,
|
|
13299
|
-
// },
|
|
13300
|
-
// },
|
|
13301
|
-
// visible: true,
|
|
13302
|
-
// },
|
|
13303
|
-
{
|
|
13304
|
-
name: 'dateFormat',
|
|
13305
|
-
title: 'Date Format',
|
|
13306
|
-
group: AXP_BEHAVIOR_PROPERTY_GROUP,
|
|
13307
|
-
schema: {
|
|
13308
|
-
defaultValue: 'dd MMM yyyy',
|
|
13309
|
-
dataType: 'string',
|
|
13310
|
-
interface: {
|
|
13311
|
-
name: 'dateFormat',
|
|
13312
|
-
path: 'options.dateFormat',
|
|
13313
|
-
type: AXPWidgetsCatalog.select,
|
|
13314
|
-
options: {
|
|
13315
|
-
dataSource: AXP_DATE_FORMAT_OPTIONS,
|
|
13316
|
-
},
|
|
13317
|
-
},
|
|
13318
|
-
},
|
|
13319
|
-
visible: true,
|
|
13320
|
-
},
|
|
13321
|
-
{
|
|
13322
|
-
name: 'timezone',
|
|
13323
|
-
title: 'Timezone',
|
|
13324
|
-
group: AXP_BEHAVIOR_PROPERTY_GROUP,
|
|
13325
|
-
schema: {
|
|
13326
|
-
defaultValue: 'local',
|
|
13327
|
-
dataType: 'string',
|
|
13328
|
-
interface: {
|
|
13329
|
-
name: 'timezone',
|
|
13330
|
-
path: 'options.timezone',
|
|
13331
|
-
type: AXPWidgetsCatalog.select,
|
|
13332
|
-
options: {
|
|
13333
|
-
dataSource: AXP_TIMEZONE_OPTIONS,
|
|
13334
|
-
},
|
|
13335
|
-
},
|
|
13336
|
-
},
|
|
13337
|
-
visible: true,
|
|
13338
|
-
},
|
|
13339
|
-
],
|
|
13340
|
-
components: {
|
|
13341
|
-
view: {
|
|
13342
|
-
component: () => Promise.resolve().then(function () { return clockCalendarWidget_component; }).then((m) => m.AXPClockCalendarWidgetViewComponent),
|
|
13343
|
-
},
|
|
13344
|
-
},
|
|
13345
|
-
meta: {
|
|
13346
|
-
dimensions: {
|
|
13347
|
-
width: 2,
|
|
13348
|
-
height: 5,
|
|
13349
|
-
minWidth: 2,
|
|
13350
|
-
minHeight: 2,
|
|
13351
|
-
maxWidth: 3,
|
|
13352
|
-
maxHeight: 6,
|
|
13353
|
-
},
|
|
13354
|
-
},
|
|
13355
|
-
};
|
|
13356
|
-
|
|
13357
|
-
/**
|
|
13358
|
-
* Donut Chart Widget Component
|
|
13359
|
-
* Displays data in a circular donut chart with interactive segments
|
|
13360
|
-
*/
|
|
13361
|
-
class AXPDonutChartWidgetViewComponent extends AXPChartBaseComponent {
|
|
13362
|
-
constructor() {
|
|
13363
|
-
super(...arguments);
|
|
13364
|
-
this.segmentClick = output();
|
|
13365
|
-
// Chart reference and D3 related properties
|
|
13366
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
13367
|
-
this.chartContainerEl = viewChild.required('chartContainer');
|
|
13368
|
-
this.pieData = [];
|
|
13369
|
-
// State management
|
|
13370
|
-
this.hiddenSegments = new Set();
|
|
13371
|
-
// Tooltip state
|
|
13372
|
-
this._tooltipVisible = signal(false);
|
|
13373
|
-
this._tooltipPosition = signal({ x: 0, y: 0 });
|
|
13374
|
-
this._tooltipData = signal({
|
|
13375
|
-
title: '',
|
|
13376
|
-
value: 0,
|
|
13377
|
-
percentage: '0%',
|
|
13378
|
-
color: '',
|
|
13379
|
-
});
|
|
13380
|
-
// Public computed properties for the template
|
|
13381
|
-
this.tooltipVisible = computed(() => this._tooltipVisible());
|
|
13382
|
-
this.tooltipPosition = computed(() => this._tooltipPosition());
|
|
13383
|
-
this.tooltipData = computed(() => this._tooltipData());
|
|
13384
|
-
// Computed configuration options
|
|
13385
|
-
this.showLegend = computed(() => this.options()['showLegend'] !== false);
|
|
13386
|
-
this.showTooltip = computed(() => this.options()['showTooltip'] !== false);
|
|
13387
|
-
this.legendPosition = computed(() => this.options()['legendPosition']?.id || 'right');
|
|
13388
|
-
this.donutWidth = computed(() => this.options()['donutWidth'] ?? 35);
|
|
13389
|
-
this.cornerRadius = computed(() => this.options()['cornerRadius'] ?? 4);
|
|
13390
|
-
// Data accessor for handling different incoming data formats
|
|
13391
|
-
this.chartDataArray = computed(() => {
|
|
13392
|
-
const data = this.chartData();
|
|
13393
|
-
if (data === null || data === undefined) {
|
|
13394
|
-
return [];
|
|
13395
|
-
}
|
|
13396
|
-
// Handle both array format and object format with data property
|
|
13397
|
-
return Array.isArray(data) ? data : data.data || [];
|
|
13398
|
-
});
|
|
13399
|
-
}
|
|
13400
|
-
getColor(index) {
|
|
13401
|
-
return AXPChartColors.getColor(index);
|
|
13402
|
-
}
|
|
13403
|
-
isSegmentHidden(id) {
|
|
13404
|
-
return this.hiddenSegments.has(id);
|
|
13405
|
-
}
|
|
13406
|
-
// Event handlers
|
|
13407
|
-
onSegmentClick(item) {
|
|
13408
|
-
this.toggleSegment(item);
|
|
13409
|
-
this.segmentClick.emit(item);
|
|
13410
|
-
}
|
|
13411
|
-
onLegendMouseEnter(item) {
|
|
13412
|
-
if (!this.svg)
|
|
13413
|
-
return;
|
|
13414
|
-
// Highlight the target segment
|
|
13415
|
-
this.svg
|
|
13416
|
-
.selectAll('path')
|
|
13417
|
-
.filter((d) => d?.data?.id === item.id)
|
|
13418
|
-
.classed('axp-donut-chart-highlighted', true)
|
|
13419
|
-
.attr('transform', 'scale(1.02)');
|
|
13420
|
-
// Dim other segments
|
|
13421
|
-
this.svg
|
|
13422
|
-
.selectAll('path')
|
|
13423
|
-
.filter((d) => d?.data?.id !== item.id)
|
|
13424
|
-
.classed('axp-donut-chart-dimmed', true);
|
|
13425
|
-
}
|
|
13426
|
-
onLegendMouseLeave() {
|
|
13427
|
-
if (!this.svg)
|
|
13428
|
-
return;
|
|
13429
|
-
// Reset all segments
|
|
13430
|
-
this.svg
|
|
13431
|
-
.selectAll('path')
|
|
13432
|
-
.classed('axp-donut-chart-highlighted', false)
|
|
13433
|
-
.classed('axp-donut-chart-dimmed', false)
|
|
13434
|
-
.attr('transform', 'scale(1)');
|
|
13435
|
-
}
|
|
13436
|
-
// Chart lifecycle methods (implementation of base class abstract methods)
|
|
13437
|
-
createChart() {
|
|
13438
|
-
if (!this.d3 || !this.chartContainerEl()?.nativeElement)
|
|
13439
|
-
return;
|
|
13440
|
-
try {
|
|
13441
|
-
const containerElement = this.chartContainerEl().nativeElement;
|
|
13442
|
-
const chartElement = containerElement.querySelector('div');
|
|
13443
|
-
this.clearChart(chartElement);
|
|
13444
|
-
const data = this.chartDataArray();
|
|
13445
|
-
if (!data || data.length === 0) {
|
|
13446
|
-
this.showNoDataMessage(chartElement);
|
|
13447
|
-
return;
|
|
13448
|
-
}
|
|
13449
|
-
const options = this.options();
|
|
13450
|
-
const { width, height } = this.setupDimensions(containerElement, options);
|
|
13451
|
-
this.renderDonutChart(chartElement, width, height);
|
|
13452
|
-
}
|
|
13453
|
-
catch (error) {
|
|
13454
|
-
console.error('Error creating donut chart:', error);
|
|
13455
|
-
this.handleChartError();
|
|
13456
|
-
}
|
|
13457
|
-
}
|
|
13458
|
-
updateChart() {
|
|
13459
|
-
this.createChart();
|
|
13460
|
-
}
|
|
13461
|
-
cleanupChart() {
|
|
13462
|
-
if (this.svg) {
|
|
13463
|
-
this.d3.select(this.chartContainerEl()?.nativeElement).selectAll('svg').remove();
|
|
13464
|
-
this.svg = null;
|
|
13465
|
-
this.pieData = [];
|
|
13466
|
-
}
|
|
13467
|
-
this.hiddenSegments.clear();
|
|
13468
|
-
this._tooltipVisible.set(false);
|
|
13469
|
-
}
|
|
13470
|
-
// Private helper methods
|
|
13471
|
-
clearChart(element) {
|
|
13472
|
-
this.d3.select(element).selectAll('*').remove();
|
|
13473
|
-
}
|
|
13474
|
-
handleChartError() {
|
|
13475
|
-
try {
|
|
13476
|
-
const containerElement = this.chartContainerEl()?.nativeElement;
|
|
13477
|
-
if (containerElement) {
|
|
13478
|
-
const chartElement = containerElement.querySelector('div');
|
|
13479
|
-
if (chartElement) {
|
|
13480
|
-
this.showNoDataMessage(chartElement);
|
|
13481
|
-
}
|
|
13482
|
-
}
|
|
13483
|
-
}
|
|
13484
|
-
catch (e) {
|
|
13485
|
-
console.error('Failed to show no data message:', e);
|
|
13486
|
-
}
|
|
13487
|
-
}
|
|
13488
|
-
setupDimensions(containerElement, options) {
|
|
13489
|
-
const containerWidth = containerElement.clientWidth;
|
|
13490
|
-
const containerHeight = containerElement.clientHeight;
|
|
13491
|
-
// Ensure minimum dimensions for the chart
|
|
13492
|
-
const minDim = 200;
|
|
13493
|
-
const width = Math.max(options['width'] || containerWidth, minDim);
|
|
13494
|
-
const height = Math.max(options['height'] || containerHeight, minDim);
|
|
13495
|
-
return { width, height };
|
|
13496
|
-
}
|
|
13497
|
-
renderDonutChart(chartElement, width, height) {
|
|
13498
|
-
const data = this.chartDataArray();
|
|
13499
|
-
// Filter out hidden segments
|
|
13500
|
-
const visibleData = data.filter((item) => !this.hiddenSegments.has(item.id));
|
|
13501
|
-
// If all segments are hidden, show no data message and return
|
|
13502
|
-
if (visibleData.length === 0) {
|
|
13503
|
-
this.showAllSegmentsHiddenMessage(chartElement);
|
|
13504
|
-
return;
|
|
13505
|
-
}
|
|
13506
|
-
const total = this.calculateTotal();
|
|
13507
|
-
// Calculate chart dimensions based on legend position
|
|
13508
|
-
const { chartWidth, chartHeight, translateX, translateY } = this.calculateChartLayout(width, height);
|
|
13509
|
-
// Create SVG container with filters
|
|
13510
|
-
const svg = this.createSvgWithFilters(chartElement, width, height);
|
|
13511
|
-
// Create main chart group
|
|
13512
|
-
this.svg = svg.append('g').attr('transform', `translate(${translateX}, ${translateY})`);
|
|
13513
|
-
// Create donut segments
|
|
13514
|
-
this.createDonutSegments(chartWidth, chartHeight, visibleData, total);
|
|
13515
|
-
// Add total in center
|
|
13516
|
-
this.addTotalDisplay(total);
|
|
13517
|
-
}
|
|
13518
|
-
calculateChartLayout(width, height) {
|
|
13519
|
-
const legendPosition = this.legendPosition();
|
|
13520
|
-
let chartWidth = width;
|
|
13521
|
-
let chartHeight = height;
|
|
13522
|
-
let translateX = width / 2;
|
|
13523
|
-
let translateY = height / 2;
|
|
13524
|
-
if (this.showLegend()) {
|
|
13525
|
-
if (legendPosition === 'right') {
|
|
13526
|
-
// Reserve space for right legend
|
|
13527
|
-
const legendWidth = Math.min(width * 0.3, 150);
|
|
13528
|
-
chartWidth = width - legendWidth - 20;
|
|
13529
|
-
translateX = (width - chartWidth) / 3 + chartWidth / 2;
|
|
13530
|
-
}
|
|
13531
|
-
else if (legendPosition === 'bottom') {
|
|
13532
|
-
// Reserve space for bottom legend
|
|
13533
|
-
const legendHeight = Math.min(height * 0.2, 80);
|
|
13534
|
-
chartHeight = height - legendHeight - 20;
|
|
13535
|
-
translateY = (height - chartHeight) / 3 + chartHeight / 2;
|
|
13536
|
-
}
|
|
13537
|
-
}
|
|
13538
|
-
return { chartWidth, chartHeight, translateX, translateY };
|
|
13539
|
-
}
|
|
13540
|
-
createSvgWithFilters(chartElement, width, height) {
|
|
13541
|
-
const svg = this.d3
|
|
13542
|
-
.select(chartElement)
|
|
13543
|
-
.append('svg')
|
|
13544
|
-
.attr('width', width)
|
|
13545
|
-
.attr('height', height)
|
|
13546
|
-
.attr('viewBox', `0 0 ${width} ${height}`)
|
|
13547
|
-
.attr('preserveAspectRatio', 'xMidYMid meet');
|
|
13548
|
-
// Add drop shadow filter
|
|
13549
|
-
const defs = svg.append('defs');
|
|
13550
|
-
const filter = defs.append('filter').attr('id', 'axp-donut-chart-segment-shadow').attr('height', '130%');
|
|
13551
|
-
filter.append('feGaussianBlur').attr('in', 'SourceAlpha').attr('stdDeviation', 2).attr('result', 'blur');
|
|
13552
|
-
filter.append('feOffset').attr('in', 'blur').attr('dx', 1).attr('dy', 1).attr('result', 'offsetBlur');
|
|
13553
|
-
const feComponentTransfer = filter
|
|
13554
|
-
.append('feComponentTransfer')
|
|
13555
|
-
.attr('in', 'offsetBlur')
|
|
13556
|
-
.attr('result', 'offsetBlur');
|
|
13557
|
-
feComponentTransfer.append('feFuncA').attr('type', 'linear').attr('slope', 0.3);
|
|
13558
|
-
const feMerge = filter.append('feMerge');
|
|
13559
|
-
feMerge.append('feMergeNode').attr('in', 'offsetBlur');
|
|
13560
|
-
feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
|
|
13561
|
-
return svg;
|
|
13562
|
-
}
|
|
13563
|
-
createDonutSegments(chartWidth, chartHeight, data, total) {
|
|
13564
|
-
// Create pie layout
|
|
13565
|
-
const pie = this.d3
|
|
13566
|
-
.pie()
|
|
13567
|
-
.value((d) => d.value)
|
|
13568
|
-
.sort(null)
|
|
13569
|
-
.padAngle(0.02);
|
|
13570
|
-
// Calculate the radius of the donut chart
|
|
13571
|
-
const radius = (Math.min(chartWidth, chartHeight) / 2) * 0.85;
|
|
13572
|
-
// Calculate inner radius based on donutWidth percentage
|
|
13573
|
-
const donutWidthPercent = this.donutWidth() / 100;
|
|
13574
|
-
const innerRadius = radius * (1 - donutWidthPercent);
|
|
13575
|
-
// Create arc generator with the configured radius and corner radius
|
|
13576
|
-
const arc = this.d3
|
|
13577
|
-
.arc()
|
|
13578
|
-
.innerRadius(innerRadius)
|
|
13579
|
-
.outerRadius(radius * 0.95)
|
|
13580
|
-
.cornerRadius(this.cornerRadius());
|
|
13581
|
-
// Generate pie data
|
|
13582
|
-
this.pieData = pie(data);
|
|
13583
|
-
// Create color scale
|
|
13584
|
-
const colorScale = this.setupColorScale(this.chartDataArray());
|
|
13585
|
-
// Add segments with animation
|
|
13586
|
-
this.addSegmentsWithAnimation(arc, colorScale, total);
|
|
13587
|
-
}
|
|
13588
|
-
setupColorScale(data) {
|
|
13589
|
-
return this.d3
|
|
13590
|
-
.scaleOrdinal()
|
|
13591
|
-
.domain(data.map((_, i) => i.toString()))
|
|
13592
|
-
.range(data.map((_, i) => this.getColor(i)));
|
|
13593
|
-
}
|
|
13594
|
-
addSegmentsWithAnimation(arc, colorScale, total) {
|
|
13595
|
-
// Add segments
|
|
13596
|
-
const segments = this.svg
|
|
13597
|
-
.selectAll('path')
|
|
13598
|
-
.data(this.pieData)
|
|
13599
|
-
.enter()
|
|
13600
|
-
.append('path')
|
|
13601
|
-
.attr('class', 'axp-donut-chart-segment')
|
|
13602
|
-
.attr('fill', (d, i) => colorScale(i.toString()))
|
|
13603
|
-
.style('opacity', 0)
|
|
13604
|
-
.on('click', (event, d) => this.onSegmentClick(d.data))
|
|
13605
|
-
.on('mousemove', (event, d) => this.handleSegmentHover(event, d, total))
|
|
13606
|
-
.on('mouseleave', () => this._tooltipVisible.set(false));
|
|
13607
|
-
// Animate segments
|
|
13608
|
-
segments
|
|
13609
|
-
.transition()
|
|
13610
|
-
.duration(800)
|
|
13611
|
-
.ease(this.d3.easeCubicOut)
|
|
13612
|
-
.delay((d, i) => i * 50)
|
|
13613
|
-
.style('opacity', 1)
|
|
13614
|
-
.attrTween('d', (d) => {
|
|
13615
|
-
const interpolate = this.d3.interpolate({ startAngle: d.startAngle, endAngle: d.startAngle }, d);
|
|
13616
|
-
return (t) => arc(interpolate(t));
|
|
13617
|
-
});
|
|
13618
|
-
}
|
|
13619
|
-
handleSegmentHover(event, datum, total) {
|
|
13620
|
-
if (!this.showTooltip())
|
|
13621
|
-
return;
|
|
13622
|
-
const percentage = ((datum.data.value / total) * 100).toFixed(1);
|
|
13623
|
-
const segmentColor = this.getColor(this.chartDataArray().findIndex((item) => item.id === datum.data.id));
|
|
13624
|
-
this._tooltipData.set({
|
|
13625
|
-
title: datum.data.name,
|
|
13626
|
-
value: datum.data.value,
|
|
13627
|
-
percentage: `${percentage}%`,
|
|
13628
|
-
color: segmentColor,
|
|
13629
|
-
});
|
|
13630
|
-
this.updateTooltipPosition(event);
|
|
13631
|
-
this._tooltipVisible.set(true);
|
|
13632
|
-
}
|
|
13633
|
-
updateTooltipPosition(event) {
|
|
13634
|
-
const container = this.chartContainerEl().nativeElement.getBoundingClientRect();
|
|
13635
|
-
const x = event.clientX - container.left;
|
|
13636
|
-
const y = event.clientY - container.top;
|
|
13637
|
-
this._tooltipPosition.set({
|
|
13638
|
-
x: x,
|
|
13639
|
-
y: y,
|
|
13640
|
-
});
|
|
13641
|
-
}
|
|
13642
|
-
toggleSegment(item) {
|
|
13643
|
-
if (this.hiddenSegments.has(item.id)) {
|
|
13644
|
-
this.hiddenSegments.delete(item.id);
|
|
13645
|
-
}
|
|
13646
|
-
else {
|
|
13647
|
-
this.hiddenSegments.add(item.id);
|
|
13648
|
-
}
|
|
13649
|
-
// Hide tooltip when toggling segments
|
|
13650
|
-
this._tooltipVisible.set(false);
|
|
13651
|
-
this.updateChart();
|
|
13652
|
-
}
|
|
13653
|
-
calculateTotal() {
|
|
13654
|
-
return this.chartDataArray()
|
|
13655
|
-
.filter((item) => !this.hiddenSegments.has(item.id))
|
|
13656
|
-
.reduce((sum, item) => sum + item.value, 0);
|
|
13657
|
-
}
|
|
13658
|
-
showNoDataMessage(containerElement) {
|
|
13659
|
-
const messageContainer = this.d3
|
|
13660
|
-
.select(containerElement)
|
|
13661
|
-
.append('div')
|
|
13662
|
-
.attr('class', 'axp-donut-chart-no-data-message')
|
|
13663
|
-
.style('width', 'auto')
|
|
13664
|
-
.style('text-align', 'center');
|
|
13665
|
-
// Add an icon
|
|
13666
|
-
messageContainer
|
|
13667
|
-
.append('div')
|
|
13668
|
-
.attr('class', 'axp-donut-chart-no-data-icon')
|
|
13669
|
-
.html('<i class="fa-light fa-chart-pie-simple fa-2x"></i>');
|
|
13670
|
-
// Add text message and help text
|
|
13671
|
-
messageContainer.append('div').attr('class', 'axp-donut-chart-no-data-text').text('No data available');
|
|
13672
|
-
messageContainer
|
|
13673
|
-
.append('div')
|
|
13674
|
-
.attr('class', 'axp-donut-chart-no-data-help')
|
|
13675
|
-
.text('Please provide data in the correct format');
|
|
13676
|
-
// Position in center
|
|
13677
|
-
const container = containerElement.getBoundingClientRect();
|
|
13678
|
-
messageContainer.style('left', `${container.width / 2}px`).style('top', `${container.height / 2}px`);
|
|
13679
|
-
}
|
|
13680
|
-
// Add method to show message when all segments are hidden
|
|
13681
|
-
showAllSegmentsHiddenMessage(containerElement) {
|
|
13682
|
-
this.clearChart(containerElement);
|
|
13683
|
-
// Add a simple div to ensure proper positioning
|
|
13684
|
-
const wrapper = this.d3
|
|
13685
|
-
.select(containerElement)
|
|
13686
|
-
.append('div')
|
|
13687
|
-
.style('position', 'relative')
|
|
13688
|
-
.style('width', '100%')
|
|
13689
|
-
.style('height', '100%');
|
|
13690
|
-
const messageContainer = wrapper
|
|
13691
|
-
.append('div')
|
|
13692
|
-
.attr('class', 'axp-donut-chart-no-data-message')
|
|
13693
|
-
.style('position', 'absolute')
|
|
13694
|
-
.style('left', '50%')
|
|
13695
|
-
.style('top', '50%')
|
|
13696
|
-
.style('transform', 'translate(-50%, -50%)')
|
|
13697
|
-
.style('text-align', 'center')
|
|
13698
|
-
.style('z-index', '10')
|
|
13699
|
-
.style('background-color', 'rgba(255, 255, 255, 0.95)')
|
|
13700
|
-
.style('padding', '1.5rem')
|
|
13701
|
-
.style('border-radius', '0.5rem')
|
|
13702
|
-
.style('box-shadow', '0 2px 12px rgba(0, 0, 0, 0.08)')
|
|
13703
|
-
.style('width', '80%')
|
|
13704
|
-
.style('max-width', '300px');
|
|
13705
|
-
// Add an icon
|
|
13706
|
-
messageContainer
|
|
13707
|
-
.append('div')
|
|
13708
|
-
.attr('class', 'axp-donut-chart-no-data-icon')
|
|
13709
|
-
.style('color', 'var(--ax-text-muted, #999)')
|
|
13710
|
-
.style('margin-bottom', '0.75rem')
|
|
13711
|
-
.html('<i class="fa-light fa-eye-slash fa-2x"></i>');
|
|
13712
|
-
// Add text message and help text
|
|
13713
|
-
messageContainer
|
|
13714
|
-
.append('div')
|
|
13715
|
-
.attr('class', 'axp-donut-chart-no-data-text')
|
|
13716
|
-
.style('font-size', '1rem')
|
|
13717
|
-
.style('font-weight', '600')
|
|
13718
|
-
.style('color', 'var(--ax-text-color, #333)')
|
|
13719
|
-
.style('margin-bottom', '0.5rem')
|
|
13720
|
-
.text('All segments are hidden');
|
|
13721
|
-
messageContainer
|
|
13722
|
-
.append('div')
|
|
13723
|
-
.attr('class', 'axp-donut-chart-no-data-help')
|
|
13724
|
-
.style('font-size', '0.8rem')
|
|
13725
|
-
.style('color', 'var(--ax-text-muted, #999)')
|
|
13726
|
-
.text('Click on a legend item to show data');
|
|
13727
|
-
}
|
|
13728
|
-
addTotalDisplay(total) {
|
|
13729
|
-
if (!this.svg)
|
|
13730
|
-
return;
|
|
13731
|
-
// Remove any existing total display
|
|
13732
|
-
this.svg.selectAll('.axp-donut-chart-total-display').remove();
|
|
13733
|
-
// Create group for the total display
|
|
13734
|
-
const totalDisplay = this.svg
|
|
13735
|
-
.append('g')
|
|
13736
|
-
.attr('class', 'axp-donut-chart-total-display')
|
|
13737
|
-
.attr('text-anchor', 'middle');
|
|
13738
|
-
// Add total value
|
|
13739
|
-
totalDisplay
|
|
13740
|
-
.append('text')
|
|
13741
|
-
.attr('class', 'axp-donut-chart-total-value')
|
|
13742
|
-
.style('font-size', '1.8rem')
|
|
13743
|
-
.style('font-weight', '600')
|
|
13744
|
-
.style('fill', 'currentColor')
|
|
13745
|
-
.text(total.toLocaleString());
|
|
13746
|
-
// Add "Total" label
|
|
13747
|
-
totalDisplay
|
|
13748
|
-
.append('text')
|
|
13749
|
-
.attr('class', 'axp-donut-chart-total-label')
|
|
13750
|
-
.attr('dy', '1.4em')
|
|
13751
|
-
.style('font-size', '0.9rem')
|
|
13752
|
-
.style('fill', 'currentColor')
|
|
13753
|
-
.style('fill-opacity', '0.8')
|
|
13754
|
-
.text('Total');
|
|
13755
|
-
}
|
|
13756
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPDonutChartWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
13757
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: AXPDonutChartWidgetViewComponent, isStandalone: true, selector: "ng-component", outputs: { segmentClick: "segmentClick" }, viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"axp-donut-chart-container\" #chartContainer>\n <!-- Chart visualization area -->\n <div class=\"axp-donut-chart-content\"></div>\n\n <!-- Legend with conditional positioning -->\n @if (showLegend()) {\n <div\n class=\"axp-donut-chart-legend-container\"\n [class.axp-donut-chart-legend-right]=\"legendPosition() === 'right'\"\n [class.axp-donut-chart-legend-bottom]=\"legendPosition() === 'bottom'\"\n >\n @for (item of chartDataArray(); track item.id) {\n <div\n class=\"axp-donut-chart-legend-item\"\n (mouseenter)=\"onLegendMouseEnter(item)\"\n (mouseleave)=\"onLegendMouseLeave()\"\n (click)=\"onSegmentClick(item)\"\n [class.axp-donut-chart-hidden]=\"isSegmentHidden(item.id)\"\n >\n <div class=\"axp-donut-chart-legend-color\" [style.background-color]=\"getColor($index)\"></div>\n <span class=\"axp-donut-chart-legend-name\">{{ item.name }}</span>\n <span class=\"axp-donut-chart-legend-value\">{{ item.value }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Shared tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"true\"\n ></ax-chart-tooltip>\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.axp-donut-chart-container{position:relative;height:100%;width:100%;padding:1rem;border-radius:.5rem;box-shadow:var(--ax-card-shadow, 0 2px 8px rgba(0, 0, 0, .05));box-sizing:border-box;display:flex;flex-direction:column;overflow:hidden}.axp-donut-chart-content{flex:1;width:100%;display:flex;align-items:center;justify-content:center;position:relative;min-height:0}.axp-donut-chart-legend-container{display:flex;gap:.75rem;padding:.75rem;flex-wrap:wrap;justify-content:center;font-family:var(--ax-font-family, system-ui, sans-serif);z-index:5}.axp-donut-chart-legend-right{flex-direction:column;position:absolute;right:.75rem;top:0;bottom:0;margin:auto;height:fit-content;max-height:80%;overflow-y:auto;overflow-x:hidden;padding:.75rem;background-color:rgba(255,255,255,.9);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border-radius:.375rem;box-shadow:0 2px 8px rgba(0,0,0,.05);max-width:30%}.axp-donut-chart-legend-bottom{position:absolute;bottom:.5rem;left:0;right:0;margin:0 auto;width:auto;max-width:98%;flex-direction:row;flex-wrap:wrap;justify-content:center;padding:.75rem;background-color:rgba(255,255,255,.9);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border-radius:.375rem;box-shadow:0 2px 8px rgba(0,0,0,.05)}.axp-donut-chart-legend-item{display:flex;align-items:center;gap:.375rem;padding:.25rem .5rem;border-radius:.375rem;cursor:pointer;transition:all .2s ease;white-space:nowrap;font-size:.875rem}.axp-donut-chart-legend-item:hover{background-color:var(--ax-hover-bg, rgba(0, 0, 0, .05));transform:translateY(-1px)}.axp-donut-chart-legend-item.axp-donut-chart-hidden{opacity:.5}.axp-donut-chart-legend-color{min-width:10px;min-height:10px;width:10px;height:10px;border-radius:2px;box-shadow:0 1px 3px rgba(0,0,0,.1)}.axp-donut-chart-legend-name{font-size:.875rem;font-weight:500;color:var(--ax-text-color, #333)}.axp-donut-chart-legend-value{font-size:.875rem;opacity:.7;margin-left:4px;font-weight:400;color:var(--ax-text-muted, #666)}.axp-donut-chart-no-data-message{position:absolute;text-align:center;transform:translate(-50%,-50%);font-family:var(--ax-font-family, system-ui, sans-serif);background-color:rgba(255,255,255,.9);padding:1.5rem;border-radius:.5rem;box-shadow:0 2px 12px rgba(0,0,0,.08);width:80%;max-width:300px}.axp-donut-chart-no-data-message .axp-donut-chart-no-data-icon{color:var(--ax-text-muted, #999);margin-bottom:.75rem}.axp-donut-chart-no-data-message .axp-donut-chart-no-data-text{font-size:1rem;font-weight:600;color:var(--ax-text-color, #333);margin-bottom:.5rem}.axp-donut-chart-no-data-message .axp-donut-chart-no-data-help{font-size:.8rem;color:var(--ax-text-muted, #999)}.axp-donut-chart-segment{cursor:pointer;transition:all .3s cubic-bezier(.25,.8,.25,1);stroke:#fff;stroke-width:1.5px;filter:drop-shadow(0px 1px 2px rgba(0,0,0,.1))}.axp-donut-chart-segment:hover{opacity:.92;filter:drop-shadow(0px 3px 5px rgba(0,0,0,.15));transform:scale(1.01)}.axp-donut-chart-highlighted{opacity:1;filter:brightness(1.05) drop-shadow(0px 3px 5px rgba(0,0,0,.15));transform:scale(1.02)}.axp-donut-chart-dimmed{opacity:.4}.axp-donut-chart-total-display{pointer-events:none}.axp-donut-chart-total-value{fill:currentColor}.axp-donut-chart-total-label{fill:currentColor;opacity:.8}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AXPChartTooltipComponent, selector: "ax-chart-tooltip", inputs: ["data", "position", "visible", "showPercentage", "style"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
13758
|
-
}
|
|
13759
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPDonutChartWidgetViewComponent, decorators: [{
|
|
13760
|
-
type: Component,
|
|
13761
|
-
args: [{ standalone: true, imports: [CommonModule, AXPChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"axp-donut-chart-container\" #chartContainer>\n <!-- Chart visualization area -->\n <div class=\"axp-donut-chart-content\"></div>\n\n <!-- Legend with conditional positioning -->\n @if (showLegend()) {\n <div\n class=\"axp-donut-chart-legend-container\"\n [class.axp-donut-chart-legend-right]=\"legendPosition() === 'right'\"\n [class.axp-donut-chart-legend-bottom]=\"legendPosition() === 'bottom'\"\n >\n @for (item of chartDataArray(); track item.id) {\n <div\n class=\"axp-donut-chart-legend-item\"\n (mouseenter)=\"onLegendMouseEnter(item)\"\n (mouseleave)=\"onLegendMouseLeave()\"\n (click)=\"onSegmentClick(item)\"\n [class.axp-donut-chart-hidden]=\"isSegmentHidden(item.id)\"\n >\n <div class=\"axp-donut-chart-legend-color\" [style.background-color]=\"getColor($index)\"></div>\n <span class=\"axp-donut-chart-legend-name\">{{ item.name }}</span>\n <span class=\"axp-donut-chart-legend-value\">{{ item.value }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Shared tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"true\"\n ></ax-chart-tooltip>\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.axp-donut-chart-container{position:relative;height:100%;width:100%;padding:1rem;border-radius:.5rem;box-shadow:var(--ax-card-shadow, 0 2px 8px rgba(0, 0, 0, .05));box-sizing:border-box;display:flex;flex-direction:column;overflow:hidden}.axp-donut-chart-content{flex:1;width:100%;display:flex;align-items:center;justify-content:center;position:relative;min-height:0}.axp-donut-chart-legend-container{display:flex;gap:.75rem;padding:.75rem;flex-wrap:wrap;justify-content:center;font-family:var(--ax-font-family, system-ui, sans-serif);z-index:5}.axp-donut-chart-legend-right{flex-direction:column;position:absolute;right:.75rem;top:0;bottom:0;margin:auto;height:fit-content;max-height:80%;overflow-y:auto;overflow-x:hidden;padding:.75rem;background-color:rgba(255,255,255,.9);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border-radius:.375rem;box-shadow:0 2px 8px rgba(0,0,0,.05);max-width:30%}.axp-donut-chart-legend-bottom{position:absolute;bottom:.5rem;left:0;right:0;margin:0 auto;width:auto;max-width:98%;flex-direction:row;flex-wrap:wrap;justify-content:center;padding:.75rem;background-color:rgba(255,255,255,.9);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);border-radius:.375rem;box-shadow:0 2px 8px rgba(0,0,0,.05)}.axp-donut-chart-legend-item{display:flex;align-items:center;gap:.375rem;padding:.25rem .5rem;border-radius:.375rem;cursor:pointer;transition:all .2s ease;white-space:nowrap;font-size:.875rem}.axp-donut-chart-legend-item:hover{background-color:var(--ax-hover-bg, rgba(0, 0, 0, .05));transform:translateY(-1px)}.axp-donut-chart-legend-item.axp-donut-chart-hidden{opacity:.5}.axp-donut-chart-legend-color{min-width:10px;min-height:10px;width:10px;height:10px;border-radius:2px;box-shadow:0 1px 3px rgba(0,0,0,.1)}.axp-donut-chart-legend-name{font-size:.875rem;font-weight:500;color:var(--ax-text-color, #333)}.axp-donut-chart-legend-value{font-size:.875rem;opacity:.7;margin-left:4px;font-weight:400;color:var(--ax-text-muted, #666)}.axp-donut-chart-no-data-message{position:absolute;text-align:center;transform:translate(-50%,-50%);font-family:var(--ax-font-family, system-ui, sans-serif);background-color:rgba(255,255,255,.9);padding:1.5rem;border-radius:.5rem;box-shadow:0 2px 12px rgba(0,0,0,.08);width:80%;max-width:300px}.axp-donut-chart-no-data-message .axp-donut-chart-no-data-icon{color:var(--ax-text-muted, #999);margin-bottom:.75rem}.axp-donut-chart-no-data-message .axp-donut-chart-no-data-text{font-size:1rem;font-weight:600;color:var(--ax-text-color, #333);margin-bottom:.5rem}.axp-donut-chart-no-data-message .axp-donut-chart-no-data-help{font-size:.8rem;color:var(--ax-text-muted, #999)}.axp-donut-chart-segment{cursor:pointer;transition:all .3s cubic-bezier(.25,.8,.25,1);stroke:#fff;stroke-width:1.5px;filter:drop-shadow(0px 1px 2px rgba(0,0,0,.1))}.axp-donut-chart-segment:hover{opacity:.92;filter:drop-shadow(0px 3px 5px rgba(0,0,0,.15));transform:scale(1.01)}.axp-donut-chart-highlighted{opacity:1;filter:brightness(1.05) drop-shadow(0px 3px 5px rgba(0,0,0,.15));transform:scale(1.02)}.axp-donut-chart-dimmed{opacity:.4}.axp-donut-chart-total-display{pointer-events:none}.axp-donut-chart-total-value{fill:currentColor}.axp-donut-chart-total-label{fill:currentColor;opacity:.8}\n"] }]
|
|
13762
|
-
}] });
|
|
13763
|
-
|
|
13764
|
-
var donutChartWidget_component = /*#__PURE__*/Object.freeze({
|
|
13765
|
-
__proto__: null,
|
|
13766
|
-
AXPDonutChartWidgetViewComponent: AXPDonutChartWidgetViewComponent
|
|
13767
|
-
});
|
|
13768
|
-
|
|
13769
|
-
const AXPDonutChartWidget = {
|
|
13770
|
-
name: 'donut-chart',
|
|
13771
|
-
title: 'Donut Chart Widget',
|
|
13772
|
-
categories: AXP_WIDGETS_CHART_CATEGORY,
|
|
13773
|
-
groups: [AXPWidgetGroupEnum.DashboardWidget],
|
|
13774
|
-
type: 'dashboard',
|
|
13775
|
-
icon: 'fa-light fa-donut',
|
|
13776
|
-
properties: [
|
|
13777
|
-
// ====== Size & Layout ======
|
|
13778
|
-
{
|
|
13779
|
-
name: 'height',
|
|
13780
|
-
title: 'Height',
|
|
13781
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
13782
|
-
schema: {
|
|
13783
|
-
defaultValue: 300,
|
|
13784
|
-
dataType: 'number',
|
|
13785
|
-
interface: {
|
|
13786
|
-
name: 'height',
|
|
13787
|
-
path: 'options.height',
|
|
13788
|
-
type: AXPWidgetsCatalog.number,
|
|
13789
|
-
options: {
|
|
13790
|
-
minValue: 200,
|
|
13791
|
-
maxValue: 800,
|
|
13792
|
-
},
|
|
13793
|
-
},
|
|
13794
|
-
},
|
|
13795
|
-
visible: true,
|
|
13796
|
-
},
|
|
13797
|
-
{
|
|
13798
|
-
name: 'width',
|
|
13799
|
-
title: 'Width',
|
|
13800
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
13801
|
-
schema: {
|
|
13802
|
-
defaultValue: 300,
|
|
13803
|
-
dataType: 'number',
|
|
13804
|
-
interface: {
|
|
13805
|
-
name: 'width',
|
|
13806
|
-
path: 'options.width',
|
|
13807
|
-
type: AXPWidgetsCatalog.number,
|
|
13808
|
-
options: {
|
|
13809
|
-
minValue: 200,
|
|
13810
|
-
maxValue: 1200,
|
|
13811
|
-
},
|
|
13812
|
-
},
|
|
13813
|
-
},
|
|
13814
|
-
visible: true,
|
|
13815
|
-
},
|
|
13816
|
-
// ====== Legend ======
|
|
13817
|
-
{
|
|
13818
|
-
name: 'showLegend',
|
|
13819
|
-
title: 'Show Legend',
|
|
13820
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13821
|
-
schema: {
|
|
13822
|
-
defaultValue: true,
|
|
13823
|
-
dataType: 'boolean',
|
|
13824
|
-
interface: {
|
|
13825
|
-
name: 'showLegend',
|
|
13826
|
-
path: 'options.showLegend',
|
|
13827
|
-
type: AXPWidgetsCatalog.toggle,
|
|
13828
|
-
},
|
|
13829
|
-
},
|
|
13830
|
-
visible: true,
|
|
13831
|
-
},
|
|
13832
|
-
{
|
|
13833
|
-
name: 'legendPosition',
|
|
13834
|
-
title: 'Legend Position',
|
|
13835
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13836
|
-
schema: {
|
|
13837
|
-
defaultValue: 'right',
|
|
13838
|
-
dataType: 'string',
|
|
13839
|
-
interface: {
|
|
13840
|
-
name: 'legendPosition',
|
|
13841
|
-
path: 'options.legendPosition',
|
|
13842
|
-
type: AXPWidgetsCatalog.select,
|
|
13843
|
-
options: {
|
|
13844
|
-
dataSource: ['right', 'bottom'],
|
|
13845
|
-
},
|
|
13846
|
-
},
|
|
13847
|
-
},
|
|
13848
|
-
visible: true,
|
|
13849
|
-
},
|
|
13850
|
-
// ====== Tooltip ======
|
|
13851
|
-
{
|
|
13852
|
-
name: 'showTooltip',
|
|
13853
|
-
title: 'Show Tooltip',
|
|
13854
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13855
|
-
schema: {
|
|
13856
|
-
defaultValue: true,
|
|
13857
|
-
dataType: 'boolean',
|
|
13858
|
-
interface: {
|
|
13859
|
-
name: 'showTooltip',
|
|
13860
|
-
path: 'options.showTooltip',
|
|
13861
|
-
type: AXPWidgetsCatalog.toggle,
|
|
13862
|
-
},
|
|
13863
|
-
},
|
|
13864
|
-
visible: true,
|
|
13865
|
-
},
|
|
13866
|
-
// ====== Donut Appearance ======
|
|
13867
|
-
{
|
|
13868
|
-
name: 'donutWidth',
|
|
13869
|
-
title: 'Donut Width',
|
|
13870
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13871
|
-
schema: {
|
|
13872
|
-
defaultValue: 35,
|
|
13873
|
-
dataType: 'number',
|
|
13874
|
-
interface: {
|
|
13875
|
-
name: 'donutWidth',
|
|
13876
|
-
path: 'options.donutWidth',
|
|
13877
|
-
type: AXPWidgetsCatalog.number,
|
|
13878
|
-
options: {
|
|
13879
|
-
placeholder: '10-80',
|
|
13880
|
-
minValue: 10,
|
|
13881
|
-
maxValue: 80,
|
|
13882
|
-
},
|
|
13883
|
-
},
|
|
13884
|
-
},
|
|
13885
|
-
visible: true,
|
|
13886
|
-
},
|
|
13887
|
-
{
|
|
13888
|
-
name: 'cornerRadius',
|
|
13889
|
-
title: 'Corner Radius',
|
|
13890
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
13891
|
-
schema: {
|
|
13892
|
-
defaultValue: 4,
|
|
13893
|
-
dataType: 'number',
|
|
13894
|
-
interface: {
|
|
13895
|
-
name: 'cornerRadius',
|
|
13896
|
-
path: 'options.cornerRadius',
|
|
13897
|
-
type: AXPWidgetsCatalog.number,
|
|
13898
|
-
options: {
|
|
13899
|
-
placeholder: '0-20',
|
|
13900
|
-
minValue: 0,
|
|
13901
|
-
maxValue: 20,
|
|
13902
|
-
},
|
|
13903
|
-
},
|
|
13904
|
-
},
|
|
13905
|
-
visible: true,
|
|
13906
|
-
},
|
|
13907
|
-
],
|
|
13908
|
-
components: {
|
|
13909
|
-
view: {
|
|
13910
|
-
component: () => Promise.resolve().then(function () { return donutChartWidget_component; }).then((c) => c.AXPDonutChartWidgetViewComponent),
|
|
13911
|
-
},
|
|
13912
|
-
},
|
|
13913
|
-
meta: {
|
|
13914
|
-
dimensions: {
|
|
13915
|
-
width: 4,
|
|
13916
|
-
height: 4,
|
|
13917
|
-
minWidth: 2,
|
|
13918
|
-
minHeight: 2,
|
|
13919
|
-
maxWidth: 5,
|
|
13920
|
-
maxHeight: 6,
|
|
13921
|
-
},
|
|
13922
|
-
},
|
|
13923
|
-
};
|
|
13924
|
-
|
|
13925
|
-
/**
|
|
13926
|
-
* Gauge Chart Widget Component
|
|
13927
|
-
* Renders a semi-circular gauge chart with animated needle and thresholds
|
|
13928
|
-
*/
|
|
13929
|
-
class AXPGaugeChartWidgetViewComponent extends AXPChartBaseComponent {
|
|
13930
|
-
constructor() {
|
|
13931
|
-
super(...arguments);
|
|
13932
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
13933
|
-
this.chartContainerEl = viewChild.required('chartContainer');
|
|
13934
|
-
this.chartEl = viewChild.required('chart');
|
|
13935
|
-
// Default values
|
|
13936
|
-
this.backgroundColor = 'transparent';
|
|
13937
|
-
this.baseColor = '#e2e8f0';
|
|
13938
|
-
this.labelFontSize = 16;
|
|
13939
|
-
this.showValue = true;
|
|
13940
|
-
this.valueColor = '#6366f1';
|
|
13941
|
-
this.valueFontSize = 24;
|
|
13942
|
-
// Default values as computed properties
|
|
13943
|
-
this.minValue = computed(() => this.options()['minValue'] ?? 0);
|
|
13944
|
-
this.maxValue = computed(() => this.options()['maxValue'] ?? 100);
|
|
13945
|
-
this.thresholds = computed(() => this.options()['thresholds'] ?? []);
|
|
13946
|
-
this.label = computed(() => this.options()['label'] ?? '');
|
|
13947
|
-
this.width = computed(() => this.options()['width'] ?? 300);
|
|
13948
|
-
this.height = computed(() => this.options()['height'] ?? 300);
|
|
13949
|
-
this.gaugeWidth = computed(() => this.options()['gaugeWidth'] ?? 22);
|
|
13950
|
-
this.cornerRadius = computed(() => this.options()['cornerRadius'] ?? 5);
|
|
13951
|
-
}
|
|
13952
|
-
/**
|
|
13953
|
-
* Creates the gauge chart with all elements
|
|
13954
|
-
*/
|
|
13955
|
-
createChart() {
|
|
13956
|
-
// Clear any existing chart
|
|
13957
|
-
this.d3.select(this.chartEl().nativeElement).selectAll('*').remove();
|
|
13958
|
-
// Calculate responsive dimensions
|
|
13959
|
-
const containerElement = this.chartContainerEl().nativeElement;
|
|
13960
|
-
const containerWidth = containerElement.clientWidth;
|
|
13961
|
-
const containerHeight = containerElement.clientHeight;
|
|
13962
|
-
// Determine size based on explicit dimensions or container size
|
|
13963
|
-
const options = this.options();
|
|
13964
|
-
let size;
|
|
13965
|
-
if (options.width && options.height) {
|
|
13966
|
-
// Use explicit dimensions if provided
|
|
13967
|
-
size = Math.min(options.width, options.height * 2);
|
|
13968
|
-
}
|
|
13969
|
-
else {
|
|
13970
|
-
// Use container dimensions with minimum constraints
|
|
13971
|
-
const minDim = Math.max(200, Math.min(containerWidth, containerHeight));
|
|
13972
|
-
size = Math.min(containerWidth, containerHeight * 2, minDim * 2);
|
|
13973
|
-
}
|
|
13974
|
-
const margin = size * 0.1;
|
|
13975
|
-
// Set up SVG with responsive viewBox
|
|
13976
|
-
const svg = this.d3
|
|
13977
|
-
.select(this.chartEl().nativeElement)
|
|
13978
|
-
.attr('width', '100%')
|
|
13979
|
-
.attr('height', '100%')
|
|
13980
|
-
.attr('viewBox', `0 0 ${size} ${size / 2}`)
|
|
13981
|
-
.attr('preserveAspectRatio', 'xMidYMid meet');
|
|
13982
|
-
// Create a group for the chart with margin and move it to show only the top half
|
|
13983
|
-
const chartGroup = svg.append('g').attr('transform', `translate(${size / 2}, ${size / 2 - margin})`);
|
|
13984
|
-
// Define gauge parameters
|
|
13985
|
-
const radius = size / 2 - margin;
|
|
13986
|
-
const innerRadius = radius - this.gaugeWidth();
|
|
13987
|
-
const outerRadius = radius;
|
|
13988
|
-
// Create gradient definitions
|
|
13989
|
-
this.createGradients(svg, this.thresholds());
|
|
13990
|
-
// Draw the background arc
|
|
13991
|
-
this.drawBackgroundArc(chartGroup, innerRadius, outerRadius, this.cornerRadius());
|
|
13992
|
-
// Draw the threshold arcs if thresholds exist
|
|
13993
|
-
if (this.thresholds().length > 0) {
|
|
13994
|
-
this.drawThresholds(chartGroup, innerRadius, outerRadius, this.minValue(), this.maxValue(), this.thresholds(), this.cornerRadius());
|
|
13995
|
-
}
|
|
13996
|
-
// Draw tick marks
|
|
13997
|
-
this.drawTicks(chartGroup, outerRadius, this.minValue(), this.maxValue());
|
|
13998
|
-
// Draw the dial/needle with animation
|
|
13999
|
-
this.drawDial(chartGroup, radius, this.chartData() ?? 0, this.minValue(), this.maxValue());
|
|
14000
|
-
// Draw the value display (after the dial so it's on top)
|
|
14001
|
-
if (this.showValue) {
|
|
14002
|
-
this.drawValueDisplay(chartGroup, this.chartData() ?? 0, this.label(), radius);
|
|
14003
|
-
}
|
|
14004
|
-
}
|
|
14005
|
-
updateChart() {
|
|
14006
|
-
this.createChart();
|
|
14007
|
-
}
|
|
14008
|
-
cleanupChart() {
|
|
14009
|
-
if (this.chartEl()?.nativeElement) {
|
|
14010
|
-
this.d3.select(this.chartEl().nativeElement).selectAll('*').remove();
|
|
14011
|
-
}
|
|
14012
|
-
}
|
|
14013
|
-
/**
|
|
14014
|
-
* Creates gradient definitions for thresholds
|
|
14015
|
-
*/
|
|
14016
|
-
createGradients(svg, thresholds) {
|
|
14017
|
-
const defs = svg.append('defs');
|
|
14018
|
-
// Create a radial gradient for the background
|
|
14019
|
-
const bgGradient = defs
|
|
14020
|
-
.append('radialGradient')
|
|
14021
|
-
.attr('id', 'gauge-bg-gradient')
|
|
14022
|
-
.attr('cx', '50%')
|
|
14023
|
-
.attr('cy', '50%')
|
|
14024
|
-
.attr('r', '50%')
|
|
14025
|
-
.attr('gradientUnits', 'userSpaceOnUse');
|
|
14026
|
-
if (thresholds.length === 0) {
|
|
14027
|
-
// Default gradient when no thresholds are provided
|
|
14028
|
-
bgGradient
|
|
14029
|
-
.append('stop')
|
|
14030
|
-
.attr('offset', '0%')
|
|
14031
|
-
.attr('stop-color', this.d3.color(this.valueColor)?.brighter(0.5).toString() || this.valueColor);
|
|
14032
|
-
bgGradient.append('stop').attr('offset', '100%').attr('stop-color', this.valueColor);
|
|
14033
|
-
}
|
|
14034
|
-
else {
|
|
14035
|
-
bgGradient
|
|
14036
|
-
.append('stop')
|
|
14037
|
-
.attr('offset', '0%')
|
|
14038
|
-
.attr('stop-color', this.d3.color(this.baseColor)?.brighter(0.5).toString() || this.baseColor);
|
|
14039
|
-
bgGradient.append('stop').attr('offset', '100%').attr('stop-color', this.baseColor);
|
|
14040
|
-
// Create gradients for each threshold
|
|
14041
|
-
thresholds.forEach((threshold, i) => {
|
|
14042
|
-
const gradient = defs
|
|
14043
|
-
.append('linearGradient')
|
|
14044
|
-
.attr('id', `threshold-gradient-${i}`)
|
|
14045
|
-
.attr('gradientUnits', 'userSpaceOnUse')
|
|
14046
|
-
.attr('x1', '-1')
|
|
14047
|
-
.attr('y1', '0')
|
|
14048
|
-
.attr('x2', '1')
|
|
14049
|
-
.attr('y2', '0');
|
|
14050
|
-
gradient
|
|
14051
|
-
.append('stop')
|
|
14052
|
-
.attr('offset', '0%')
|
|
14053
|
-
.attr('stop-color', this.d3.color(threshold.color)?.brighter(0.5).toString() || threshold.color);
|
|
14054
|
-
gradient.append('stop').attr('offset', '100%').attr('stop-color', threshold.color);
|
|
14055
|
-
});
|
|
14056
|
-
}
|
|
14057
|
-
}
|
|
14058
|
-
/**
|
|
14059
|
-
* Draws the background arc
|
|
14060
|
-
*/
|
|
14061
|
-
drawBackgroundArc(chartGroup, innerRadius, outerRadius, cornerRadius) {
|
|
14062
|
-
const backgroundArc = this.d3
|
|
14063
|
-
.arc()
|
|
14064
|
-
.innerRadius(innerRadius)
|
|
14065
|
-
.outerRadius(outerRadius)
|
|
14066
|
-
.startAngle(-Math.PI / 2) // Start from bottom (-90 degrees)
|
|
14067
|
-
.endAngle(Math.PI / 2) // End at top (90 degrees)
|
|
14068
|
-
.cornerRadius(cornerRadius);
|
|
14069
|
-
chartGroup
|
|
14070
|
-
.append('path')
|
|
14071
|
-
.attr('d', backgroundArc({
|
|
14072
|
-
innerRadius,
|
|
14073
|
-
outerRadius,
|
|
14074
|
-
startAngle: -Math.PI / 2,
|
|
14075
|
-
endAngle: Math.PI / 2,
|
|
14076
|
-
}))
|
|
14077
|
-
.attr('fill', this.backgroundColor === 'transparent' ? 'url(#gauge-bg-gradient)' : this.backgroundColor)
|
|
14078
|
-
.attr('filter', 'drop-shadow(0px 2px 3px rgba(0,0,0,0.1))');
|
|
14079
|
-
}
|
|
14080
|
-
/**
|
|
14081
|
-
* Draws the threshold arcs with colors
|
|
14082
|
-
*/
|
|
14083
|
-
drawThresholds(chartGroup, innerRadius, outerRadius, minValue, maxValue, thresholds, cornerRadius) {
|
|
14084
|
-
const arc = this.d3
|
|
14085
|
-
.arc()
|
|
14086
|
-
.innerRadius(innerRadius)
|
|
14087
|
-
.outerRadius(outerRadius * 0.98)
|
|
14088
|
-
.cornerRadius(cornerRadius);
|
|
14089
|
-
// Sort thresholds by value in ascending order
|
|
14090
|
-
const sortedThresholds = [...thresholds].sort((a, b) => a.value - b.value);
|
|
14091
|
-
// Calculate all angles first using the color angle calculation
|
|
14092
|
-
const angles = sortedThresholds.map((t) => this.scaleValueToColorAngle(t.value, minValue, maxValue));
|
|
14093
|
-
// Start from the minimum value angle
|
|
14094
|
-
let previousEndAngle = this.scaleValueToColorAngle(minValue, minValue, maxValue);
|
|
14095
|
-
sortedThresholds.forEach((threshold, i) => {
|
|
14096
|
-
const endAngle = angles[i];
|
|
14097
|
-
chartGroup
|
|
14098
|
-
.append('path')
|
|
14099
|
-
.attr('d', arc({
|
|
14100
|
-
innerRadius,
|
|
14101
|
-
outerRadius,
|
|
14102
|
-
startAngle: previousEndAngle,
|
|
14103
|
-
endAngle,
|
|
14104
|
-
}))
|
|
14105
|
-
.attr('fill', `url(#threshold-gradient-${i})`)
|
|
14106
|
-
.attr('stroke', 'rgba(255,255,255,0.3)')
|
|
14107
|
-
.attr('stroke-width', 1);
|
|
14108
|
-
previousEndAngle = endAngle;
|
|
14109
|
-
});
|
|
14110
|
-
// Fill the remaining space to maxValue if needed
|
|
14111
|
-
const lastEndAngle = this.scaleValueToColorAngle(maxValue, minValue, maxValue);
|
|
14112
|
-
if (previousEndAngle < lastEndAngle) {
|
|
14113
|
-
chartGroup
|
|
14114
|
-
.append('path')
|
|
14115
|
-
.attr('d', arc({
|
|
14116
|
-
innerRadius,
|
|
14117
|
-
outerRadius,
|
|
14118
|
-
startAngle: previousEndAngle,
|
|
14119
|
-
endAngle: lastEndAngle,
|
|
14120
|
-
}))
|
|
14121
|
-
.attr('fill', `url(#threshold-gradient-${sortedThresholds.length - 1})`)
|
|
14122
|
-
.attr('stroke', 'rgba(255,255,255,0.3)')
|
|
14123
|
-
.attr('stroke-width', 1);
|
|
14124
|
-
}
|
|
14125
|
-
}
|
|
14126
|
-
/**
|
|
14127
|
-
* Draws tick marks around the gauge
|
|
14128
|
-
*/
|
|
14129
|
-
drawTicks(chartGroup, radius, minValue, maxValue) {
|
|
14130
|
-
const tickCount = 10;
|
|
14131
|
-
const minorTickCount = 40;
|
|
14132
|
-
// Draw minor ticks
|
|
14133
|
-
for (let i = 0; i <= minorTickCount; i++) {
|
|
14134
|
-
const value = minValue + (i / minorTickCount) * (maxValue - minValue);
|
|
14135
|
-
const angle = this.scaleValueToAngle(value, minValue, maxValue);
|
|
14136
|
-
const outerPoint = {
|
|
14137
|
-
x: radius * 0.9 * Math.cos(angle),
|
|
14138
|
-
y: radius * 0.9 * Math.sin(angle),
|
|
14139
|
-
};
|
|
14140
|
-
const innerPoint = {
|
|
14141
|
-
x: radius * 0.87 * Math.cos(angle),
|
|
14142
|
-
y: radius * 0.87 * Math.sin(angle),
|
|
14143
|
-
};
|
|
14144
|
-
chartGroup
|
|
14145
|
-
.append('line')
|
|
14146
|
-
.attr('x1', innerPoint.x)
|
|
14147
|
-
.attr('y1', innerPoint.y)
|
|
14148
|
-
.attr('x2', outerPoint.x)
|
|
14149
|
-
.attr('y2', outerPoint.y)
|
|
14150
|
-
.attr('stroke', '#adb5bd')
|
|
14151
|
-
.attr('stroke-width', 0.5);
|
|
14152
|
-
}
|
|
14153
|
-
// Draw major ticks and labels
|
|
14154
|
-
for (let i = 0; i <= tickCount; i++) {
|
|
14155
|
-
const value = minValue + (i / tickCount) * (maxValue - minValue);
|
|
14156
|
-
const angle = this.scaleValueToAngle(value, minValue, maxValue);
|
|
14157
|
-
const outerPoint = {
|
|
14158
|
-
x: radius * 0.92 * Math.cos(angle),
|
|
14159
|
-
y: radius * 0.92 * Math.sin(angle),
|
|
14160
|
-
};
|
|
14161
|
-
const innerPoint = {
|
|
14162
|
-
x: radius * 0.85 * Math.cos(angle),
|
|
14163
|
-
y: radius * 0.85 * Math.sin(angle),
|
|
14164
|
-
};
|
|
14165
|
-
// Major tick line
|
|
14166
|
-
chartGroup
|
|
14167
|
-
.append('line')
|
|
14168
|
-
.attr('x1', innerPoint.x)
|
|
14169
|
-
.attr('y1', innerPoint.y)
|
|
14170
|
-
.attr('x2', outerPoint.x)
|
|
14171
|
-
.attr('y2', outerPoint.y)
|
|
14172
|
-
.attr('stroke', '#adb5bd')
|
|
14173
|
-
.attr('stroke-width', 2);
|
|
14174
|
-
// Label position with offset
|
|
14175
|
-
const labelRadius = radius * 1.15;
|
|
14176
|
-
const labelX = labelRadius * Math.cos(angle);
|
|
14177
|
-
const labelY = labelRadius * Math.sin(angle);
|
|
14178
|
-
// Add tick value label
|
|
14179
|
-
chartGroup
|
|
14180
|
-
.append('text')
|
|
14181
|
-
.attr('x', labelX)
|
|
14182
|
-
.attr('y', labelY)
|
|
14183
|
-
.attr('text-anchor', 'middle')
|
|
14184
|
-
.style('font-size', `${radius * 0.1}px`)
|
|
14185
|
-
.style('font-weight', '500')
|
|
14186
|
-
.style('fill', 'currentColor')
|
|
14187
|
-
.text(value.toFixed(0));
|
|
14188
|
-
}
|
|
14189
|
-
}
|
|
14190
|
-
/**
|
|
14191
|
-
* Draws the value display in the center
|
|
14192
|
-
*/
|
|
14193
|
-
drawValueDisplay(chartGroup, value, label, radius) {
|
|
14194
|
-
// Value text - positioned below the needle pivot
|
|
14195
|
-
chartGroup
|
|
14196
|
-
.append('text')
|
|
14197
|
-
.attr('class', 'gauge-value')
|
|
14198
|
-
.attr('x', 0)
|
|
14199
|
-
.attr('y', radius * 0.25) // Moved up from 0.3
|
|
14200
|
-
.attr('text-anchor', 'middle')
|
|
14201
|
-
.attr('dominant-baseline', 'central')
|
|
14202
|
-
.style('font-size', `${this.valueFontSize}px`)
|
|
14203
|
-
.style('font-weight', 'bold')
|
|
14204
|
-
.style('fill', 'currentColor')
|
|
14205
|
-
.text(value.toFixed(1));
|
|
14206
|
-
// Label text
|
|
14207
|
-
chartGroup
|
|
14208
|
-
.append('text')
|
|
14209
|
-
.attr('class', 'gauge-label')
|
|
14210
|
-
.attr('x', 0)
|
|
14211
|
-
.attr('y', radius * 0.45) // Keeping this position to create more space
|
|
14212
|
-
.attr('text-anchor', 'middle')
|
|
14213
|
-
.attr('dominant-baseline', 'central')
|
|
14214
|
-
.style('font-size', `${this.labelFontSize}px`)
|
|
14215
|
-
.style('fill', '#6c757d')
|
|
14216
|
-
.text(label);
|
|
14217
|
-
}
|
|
14218
|
-
/**
|
|
14219
|
-
* Draws the dial/needle with animation
|
|
14220
|
-
*/
|
|
14221
|
-
drawDial(chartGroup, radius, value, minValue, maxValue) {
|
|
14222
|
-
const needleGroup = chartGroup.append('g');
|
|
14223
|
-
const valueAngle = this.scaleValueToAngle(value, minValue, maxValue); // Use regular angle for needle
|
|
14224
|
-
// Draw needle base (circle)
|
|
14225
|
-
needleGroup
|
|
14226
|
-
.append('circle')
|
|
14227
|
-
.attr('cx', 0)
|
|
14228
|
-
.attr('cy', 0)
|
|
14229
|
-
.attr('r', radius * 0.08)
|
|
14230
|
-
.attr('fill', 'url(#gauge-bg-gradient)')
|
|
14231
|
-
.attr('stroke', '#6c757d')
|
|
14232
|
-
.attr('stroke-width', 1);
|
|
14233
|
-
// Inner circle
|
|
14234
|
-
needleGroup
|
|
14235
|
-
.append('circle')
|
|
14236
|
-
.attr('cx', 0)
|
|
14237
|
-
.attr('cy', 0)
|
|
14238
|
-
.attr('r', radius * 0.04)
|
|
14239
|
-
.attr('fill', '#495057');
|
|
14240
|
-
// Create needle
|
|
14241
|
-
const needlePath = needleGroup
|
|
14242
|
-
.append('path')
|
|
14243
|
-
.attr('d', this.createNeedlePath(radius))
|
|
14244
|
-
.attr('fill', '#dc3545')
|
|
14245
|
-
.attr('transform', `rotate(${this.radiansToDegrees(-Math.PI)})`) // Start at left (-180 degrees)
|
|
14246
|
-
.attr('filter', 'drop-shadow(0px 1px 2px rgba(0,0,0,0.3))');
|
|
14247
|
-
// Animate the needle
|
|
14248
|
-
needlePath
|
|
14249
|
-
.transition()
|
|
14250
|
-
.duration(800)
|
|
14251
|
-
.ease(this.d3.easeCubicOut)
|
|
14252
|
-
.attrTween('transform', () => {
|
|
14253
|
-
const interpolate = this.d3.interpolate(-Math.PI, valueAngle);
|
|
14254
|
-
return (t) => `rotate(${this.radiansToDegrees(interpolate(t))})`;
|
|
14255
|
-
});
|
|
14256
|
-
}
|
|
14257
|
-
/**
|
|
14258
|
-
* Creates a path for the needle
|
|
14259
|
-
*/
|
|
14260
|
-
createNeedlePath(radius) {
|
|
14261
|
-
const needleLength = radius * 0.75;
|
|
14262
|
-
const needleBaseWidth = radius * 0.04;
|
|
14263
|
-
return `M 0 -${needleBaseWidth} L ${needleLength} 0 L 0 ${needleBaseWidth} Z`;
|
|
14264
|
-
}
|
|
14265
|
-
/**
|
|
14266
|
-
* Scales a value to an angle for needle positioning
|
|
14267
|
-
*/
|
|
14268
|
-
scaleValueToAngle(value, min, max) {
|
|
14269
|
-
const scaledValue = (value - min) / (max - min);
|
|
14270
|
-
// Map from -180 to 0 degrees in radians, starting from the left
|
|
14271
|
-
return -Math.PI + scaledValue * Math.PI;
|
|
14272
|
-
}
|
|
14273
|
-
/**
|
|
14274
|
-
* Scales a value to an angle for threshold colors
|
|
14275
|
-
*/
|
|
14276
|
-
scaleValueToColorAngle(value, min, max) {
|
|
14277
|
-
const scaledValue = (value - min) / (max - min);
|
|
14278
|
-
// Map from -90 to 90 degrees in radians (-π/2 to π/2), starting from the bottom
|
|
14279
|
-
return -Math.PI / 2 + scaledValue * Math.PI;
|
|
14280
|
-
}
|
|
14281
|
-
/**
|
|
14282
|
-
* Converts radians to degrees
|
|
14283
|
-
*/
|
|
14284
|
-
radiansToDegrees(radians) {
|
|
14285
|
-
return radians * (180 / Math.PI);
|
|
14286
|
-
}
|
|
14287
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPGaugeChartWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
14288
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.0.3", type: AXPGaugeChartWidgetViewComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }, { propertyName: "chartEl", first: true, predicate: ["chart"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-flex ax-justify-center ax-items-center ax-w-full ax-h-full\" #chartContainer>\n <svg #chart class=\"ax-w-full ax-h-full\"></svg>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px}div.ax-flex{position:relative;width:100%;height:100%;overflow:visible}svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}.gauge-arc{transition:stroke-dashoffset 1s ease}.gauge-label{font-family:var(--ax-font-family, system-ui, sans-serif);font-weight:600}.gauge-value{font-family:var(--ax-font-family, system-ui, sans-serif);font-weight:700}.gauge-min-max{font-family:var(--ax-font-family, system-ui, sans-serif);opacity:.7}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
14289
|
-
}
|
|
14290
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPGaugeChartWidgetViewComponent, decorators: [{
|
|
14291
|
-
type: Component,
|
|
14292
|
-
args: [{ standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-flex ax-justify-center ax-items-center ax-w-full ax-h-full\" #chartContainer>\n <svg #chart class=\"ax-w-full ax-h-full\"></svg>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px}div.ax-flex{position:relative;width:100%;height:100%;overflow:visible}svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}.gauge-arc{transition:stroke-dashoffset 1s ease}.gauge-label{font-family:var(--ax-font-family, system-ui, sans-serif);font-weight:600}.gauge-value{font-family:var(--ax-font-family, system-ui, sans-serif);font-weight:700}.gauge-min-max{font-family:var(--ax-font-family, system-ui, sans-serif);opacity:.7}\n"] }]
|
|
14293
|
-
}] });
|
|
14294
|
-
|
|
14295
|
-
var gaugeChartWidget_component = /*#__PURE__*/Object.freeze({
|
|
14296
|
-
__proto__: null,
|
|
14297
|
-
AXPGaugeChartWidgetViewComponent: AXPGaugeChartWidgetViewComponent
|
|
14298
|
-
});
|
|
14299
|
-
|
|
14300
|
-
const AXPGaugeChartWidget = {
|
|
14301
|
-
name: 'gauge-chart',
|
|
14302
|
-
title: 'Gauge Chart Widget',
|
|
14303
|
-
categories: [AXP_WIDGETS_CHART_CATEGORY],
|
|
14304
|
-
type: 'dashboard',
|
|
14305
|
-
icon: 'fa-light fa-gauge',
|
|
14306
|
-
properties: [
|
|
14307
|
-
// ====== Layout & Dimensions ======
|
|
14308
|
-
{
|
|
14309
|
-
name: 'height',
|
|
14310
|
-
title: 'Height',
|
|
14311
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
14312
|
-
schema: {
|
|
14313
|
-
defaultValue: 300,
|
|
14314
|
-
dataType: 'number',
|
|
14315
|
-
interface: {
|
|
14316
|
-
name: 'height',
|
|
14317
|
-
path: 'options.height',
|
|
14318
|
-
type: AXPWidgetsCatalog.number,
|
|
14319
|
-
options: {
|
|
14320
|
-
placeholder: '1-800',
|
|
14321
|
-
minValue: 1,
|
|
14322
|
-
maxValue: 800,
|
|
14323
|
-
},
|
|
14324
|
-
},
|
|
14325
|
-
},
|
|
14326
|
-
visible: true,
|
|
14327
|
-
},
|
|
14328
|
-
{
|
|
14329
|
-
name: 'width',
|
|
14330
|
-
title: 'Width',
|
|
14331
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
14332
|
-
schema: {
|
|
14333
|
-
defaultValue: null,
|
|
14334
|
-
dataType: 'number',
|
|
14335
|
-
interface: {
|
|
14336
|
-
name: 'width',
|
|
14337
|
-
path: 'options.width',
|
|
14338
|
-
type: AXPWidgetsCatalog.number,
|
|
14339
|
-
options: {
|
|
14340
|
-
placeholder: '1-1200',
|
|
14341
|
-
minValue: 1,
|
|
14342
|
-
maxValue: 1200,
|
|
14343
|
-
},
|
|
14344
|
-
},
|
|
14345
|
-
},
|
|
14346
|
-
visible: true,
|
|
14347
|
-
},
|
|
14348
|
-
// ====== Gauge Configuration ======
|
|
14349
|
-
{
|
|
14350
|
-
name: 'minValue',
|
|
14351
|
-
title: 'Minimum Value',
|
|
14352
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14353
|
-
schema: {
|
|
14354
|
-
defaultValue: 0,
|
|
14355
|
-
dataType: 'number',
|
|
14356
|
-
interface: {
|
|
14357
|
-
name: 'minValue',
|
|
14358
|
-
path: 'options.minValue',
|
|
14359
|
-
type: AXPWidgetsCatalog.number,
|
|
14360
|
-
},
|
|
14361
|
-
},
|
|
14362
|
-
visible: true,
|
|
14363
|
-
},
|
|
14364
|
-
{
|
|
14365
|
-
name: 'maxValue',
|
|
14366
|
-
title: 'Maximum Value',
|
|
14367
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14368
|
-
schema: {
|
|
14369
|
-
defaultValue: 100,
|
|
14370
|
-
dataType: 'number',
|
|
14371
|
-
interface: {
|
|
14372
|
-
name: 'maxValue',
|
|
14373
|
-
path: 'options.maxValue',
|
|
14374
|
-
type: AXPWidgetsCatalog.number,
|
|
14375
|
-
},
|
|
14376
|
-
},
|
|
14377
|
-
visible: true,
|
|
14378
|
-
},
|
|
14379
|
-
// ====== Gauge Appearance ======
|
|
14380
|
-
{
|
|
14381
|
-
name: 'gaugeWidth',
|
|
14382
|
-
title: 'Gauge Width',
|
|
14383
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14384
|
-
schema: {
|
|
14385
|
-
defaultValue: 30,
|
|
14386
|
-
dataType: 'number',
|
|
14387
|
-
interface: {
|
|
14388
|
-
name: 'gaugeWidth',
|
|
14389
|
-
path: 'options.gaugeWidth',
|
|
14390
|
-
type: AXPWidgetsCatalog.number,
|
|
14391
|
-
options: {
|
|
14392
|
-
placeholder: '1-100',
|
|
14393
|
-
minValue: 1,
|
|
14394
|
-
maxValue: 100,
|
|
14395
|
-
},
|
|
14396
|
-
},
|
|
14397
|
-
},
|
|
14398
|
-
visible: true,
|
|
14399
|
-
},
|
|
14400
|
-
{
|
|
14401
|
-
name: 'cornerRadius',
|
|
14402
|
-
title: 'Corner Radius',
|
|
14403
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14404
|
-
schema: {
|
|
14405
|
-
defaultValue: 4,
|
|
14406
|
-
dataType: 'number',
|
|
14407
|
-
interface: {
|
|
14408
|
-
name: 'cornerRadius',
|
|
14409
|
-
path: 'options.cornerRadius',
|
|
14410
|
-
type: AXPWidgetsCatalog.number,
|
|
14411
|
-
options: {
|
|
14412
|
-
placeholder: '1-20',
|
|
14413
|
-
minValue: 0,
|
|
14414
|
-
maxValue: 20,
|
|
14415
|
-
},
|
|
14416
|
-
},
|
|
14417
|
-
},
|
|
14418
|
-
visible: true,
|
|
14419
|
-
},
|
|
14420
|
-
// ====== Label Display ======
|
|
14421
|
-
{
|
|
14422
|
-
name: 'label',
|
|
14423
|
-
title: 'Label Text',
|
|
14424
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14425
|
-
schema: {
|
|
14426
|
-
defaultValue: '',
|
|
14427
|
-
dataType: 'string',
|
|
14428
|
-
interface: {
|
|
14429
|
-
name: 'label',
|
|
14430
|
-
path: 'options.label',
|
|
14431
|
-
type: AXPWidgetsCatalog.text,
|
|
14432
|
-
},
|
|
14433
|
-
},
|
|
14434
|
-
visible: true,
|
|
14435
|
-
},
|
|
14436
|
-
],
|
|
14437
|
-
components: {
|
|
14438
|
-
view: {
|
|
14439
|
-
component: () => Promise.resolve().then(function () { return gaugeChartWidget_component; }).then((c) => c.AXPGaugeChartWidgetViewComponent),
|
|
14440
|
-
},
|
|
14441
|
-
},
|
|
14442
|
-
meta: {
|
|
14443
|
-
dimensions: {
|
|
14444
|
-
width: 3,
|
|
14445
|
-
height: 4,
|
|
14446
|
-
minWidth: 2,
|
|
14447
|
-
minHeight: 2,
|
|
14448
|
-
maxWidth: 4,
|
|
14449
|
-
maxHeight: 5,
|
|
14450
|
-
},
|
|
14451
|
-
},
|
|
14452
|
-
};
|
|
14453
|
-
|
|
14454
|
-
/**
|
|
14455
|
-
* Line Chart Widget Component
|
|
14456
|
-
* Renders data as lines with interactive hover effects and animations
|
|
14457
|
-
*/
|
|
14458
|
-
class AXPLineChartWidgetViewComponent extends AXPChartBaseComponent {
|
|
14459
|
-
constructor() {
|
|
14460
|
-
super(...arguments);
|
|
14461
|
-
this.pointClick = output();
|
|
14462
|
-
// Chart container reference
|
|
14463
|
-
this.chartContainerEl = viewChild.required('chartContainer');
|
|
14464
|
-
this.margin = { top: 20, right: 20, bottom: 30, left: 40 };
|
|
14465
|
-
// Tooltip state
|
|
14466
|
-
this._tooltipVisible = signal(false);
|
|
14467
|
-
this._tooltipPosition = signal({ x: 0, y: 0 });
|
|
14468
|
-
this._tooltipData = signal({
|
|
14469
|
-
title: '',
|
|
14470
|
-
value: '0',
|
|
14471
|
-
percentage: '0%',
|
|
14472
|
-
color: '',
|
|
14473
|
-
});
|
|
14474
|
-
// Tooltip accessors
|
|
14475
|
-
this.tooltipVisible = computed(() => this._tooltipVisible());
|
|
14476
|
-
this.tooltipPosition = computed(() => this._tooltipPosition());
|
|
14477
|
-
this.tooltipData = computed(() => this._tooltipData());
|
|
14478
|
-
// Line appearance options
|
|
14479
|
-
this.lineWidth = computed(() => this.options()['lineWidth'] ?? 2);
|
|
14480
|
-
this.showPoints = computed(() => this.options()['showPoints'] !== false);
|
|
14481
|
-
this.pointRadius = computed(() => this.options()['pointRadius'] ?? 4);
|
|
14482
|
-
this.smoothLine = computed(() => this.options()['smoothLine'] !== false);
|
|
14483
|
-
this.fillArea = computed(() => this.options()['fillArea'] === true);
|
|
14484
|
-
this.fillOpacity = computed(() => (this.options()['fillOpacity'] ?? 20) / 100);
|
|
14485
|
-
}
|
|
14486
|
-
// Chart lifecycle methods
|
|
14487
|
-
/**
|
|
14488
|
-
* Creates the line chart SVG and renders all elements
|
|
14489
|
-
*/
|
|
14490
|
-
createChart() {
|
|
14491
|
-
if (!this.d3 || !this.chartContainerEl()?.nativeElement)
|
|
14492
|
-
return;
|
|
14493
|
-
const containerElement = this.chartContainerEl().nativeElement;
|
|
14494
|
-
let data = this.chartData() || [];
|
|
14495
|
-
// Clear existing chart
|
|
14496
|
-
this.d3.select(containerElement).selectAll('svg').remove();
|
|
14497
|
-
// Normalize data structure to array of arrays (multi-series format)
|
|
14498
|
-
let normalizedData = [];
|
|
14499
|
-
// Handle both flat array and array of arrays formats
|
|
14500
|
-
if (data.length > 0) {
|
|
14501
|
-
if (Array.isArray(data[0])) {
|
|
14502
|
-
// Already in multi-series format
|
|
14503
|
-
normalizedData = data;
|
|
14504
|
-
}
|
|
14505
|
-
else {
|
|
14506
|
-
// Single series or flat array with seriesName
|
|
14507
|
-
const dataPoints = data;
|
|
14508
|
-
// Check if this is multi-series data (has seriesName property)
|
|
14509
|
-
const hasSeriesNames = dataPoints.some((d) => d.seriesName !== undefined);
|
|
14510
|
-
if (hasSeriesNames) {
|
|
14511
|
-
// Organize by series name
|
|
14512
|
-
const seriesMap = new Map();
|
|
14513
|
-
dataPoints.forEach((d) => {
|
|
14514
|
-
const seriesName = d.seriesName || 'default';
|
|
14515
|
-
if (!seriesMap.has(seriesName)) {
|
|
14516
|
-
seriesMap.set(seriesName, []);
|
|
14517
|
-
}
|
|
14518
|
-
seriesMap.get(seriesName).push(d);
|
|
14519
|
-
});
|
|
14520
|
-
// Convert to array of arrays
|
|
14521
|
-
normalizedData = Array.from(seriesMap.values());
|
|
14522
|
-
}
|
|
14523
|
-
else {
|
|
14524
|
-
// Single series, wrap it in an array
|
|
14525
|
-
normalizedData = [dataPoints];
|
|
14526
|
-
}
|
|
14527
|
-
}
|
|
14528
|
-
}
|
|
14529
|
-
// Early return if no data
|
|
14530
|
-
if (normalizedData.length === 0 || normalizedData.flat().length === 0) {
|
|
14531
|
-
this.showNoDataMessage(containerElement);
|
|
14532
|
-
return;
|
|
14533
|
-
}
|
|
14534
|
-
// Get options and setup dimensions
|
|
14535
|
-
const options = this.options();
|
|
14536
|
-
this.setupDimensions(containerElement, options);
|
|
14537
|
-
// Flatten data for scale calculation
|
|
14538
|
-
const flatData = normalizedData.flat();
|
|
14539
|
-
// Create scales with the flattened data
|
|
14540
|
-
this.setupScales(flatData);
|
|
14541
|
-
this.createAxes(options);
|
|
14542
|
-
// Render the lines using the structured data (array of arrays)
|
|
14543
|
-
this.renderLines(normalizedData);
|
|
14544
|
-
}
|
|
14545
|
-
updateChart() {
|
|
14546
|
-
this.createChart();
|
|
14547
|
-
}
|
|
14548
|
-
cleanupChart() {
|
|
14549
|
-
if (this.svg) {
|
|
14550
|
-
this.d3.select(this.chartContainerEl()?.nativeElement).selectAll('svg').remove();
|
|
14551
|
-
this.svg = null;
|
|
14552
|
-
this.chart = null;
|
|
14553
|
-
}
|
|
14554
|
-
this._tooltipVisible.set(false);
|
|
14555
|
-
}
|
|
14556
|
-
// Private chart creation methods
|
|
14557
|
-
/**
|
|
14558
|
-
* Sets up chart dimensions and creates SVG with responsive attributes
|
|
14559
|
-
*/
|
|
14560
|
-
setupDimensions(containerElement, options) {
|
|
14561
|
-
// Get container dimensions
|
|
14562
|
-
const containerWidth = containerElement.clientWidth;
|
|
14563
|
-
const containerHeight = containerElement.clientHeight;
|
|
14564
|
-
// If options specify width and height, use those, otherwise default to container size
|
|
14565
|
-
const minDim = Math.min(200, containerWidth, containerHeight); // Ensure reasonable minimum
|
|
14566
|
-
if (options.width && options.height) {
|
|
14567
|
-
// Explicit dimensions provided
|
|
14568
|
-
this.width = options.width - this.margin.left - this.margin.right;
|
|
14569
|
-
this.height = options.height - this.margin.top - this.margin.bottom;
|
|
14570
|
-
}
|
|
14571
|
-
else {
|
|
14572
|
-
// Responsive dimensions
|
|
14573
|
-
this.width = Math.max(containerWidth, minDim) - this.margin.left - this.margin.right;
|
|
14574
|
-
this.height = Math.max(containerHeight, minDim) - this.margin.top - this.margin.bottom;
|
|
14575
|
-
}
|
|
14576
|
-
// Create responsive SVG that scales with its container
|
|
14577
|
-
const svg = this.d3
|
|
14578
|
-
.select(containerElement)
|
|
14579
|
-
.append('svg')
|
|
14580
|
-
.attr('width', '100%')
|
|
14581
|
-
.attr('height', '100%')
|
|
14582
|
-
.attr('viewBox', `0 0 ${this.width + this.margin.left + this.margin.right} ${this.height + this.margin.top + this.margin.bottom}`)
|
|
14583
|
-
.attr('preserveAspectRatio', 'xMidYMid meet');
|
|
14584
|
-
this.svg = svg;
|
|
14585
|
-
// Create chart group with margins
|
|
14586
|
-
this.chart = this.svg.append('g').attr('transform', `translate(${this.margin.left},${this.margin.top})`);
|
|
14587
|
-
}
|
|
14588
|
-
/**
|
|
14589
|
-
* Creates x and y scales for the chart
|
|
14590
|
-
*/
|
|
14591
|
-
setupScales(data) {
|
|
14592
|
-
// Determine x-axis type based on data
|
|
14593
|
-
const allNumericX = data.every((d) => typeof d.xValue === 'number');
|
|
14594
|
-
const allDates = data.every((d) => !isNaN(new Date(d.xValue).getTime()));
|
|
14595
|
-
// Create appropriate x scale based on data type
|
|
14596
|
-
if (allNumericX) {
|
|
14597
|
-
// Numeric x-axis
|
|
14598
|
-
this.xScale = this.d3
|
|
14599
|
-
.scaleLinear()
|
|
14600
|
-
.domain([
|
|
14601
|
-
this.d3.min(data, (d) => d.xValue) || 0,
|
|
14602
|
-
this.d3.max(data, (d) => d.xValue) || 0,
|
|
14603
|
-
])
|
|
14604
|
-
.range([0, this.width]);
|
|
14605
|
-
}
|
|
14606
|
-
else if (allDates) {
|
|
14607
|
-
// Date x-axis
|
|
14608
|
-
this.xScale = this.d3
|
|
14609
|
-
.scaleTime()
|
|
14610
|
-
.domain([
|
|
14611
|
-
this.d3.min(data, (d) => new Date(d.xValue)) || new Date(),
|
|
14612
|
-
this.d3.max(data, (d) => new Date(d.xValue)) || new Date(),
|
|
14613
|
-
])
|
|
14614
|
-
.range([0, this.width]);
|
|
14615
|
-
}
|
|
14616
|
-
else {
|
|
14617
|
-
// Categorical x-axis
|
|
14618
|
-
this.xScale = this.d3
|
|
14619
|
-
.scaleBand()
|
|
14620
|
-
.domain(data.map((d) => d.xValue.toString()))
|
|
14621
|
-
.range([0, this.width])
|
|
14622
|
-
.padding(0.1);
|
|
14623
|
-
}
|
|
14624
|
-
// Create y scale (linear scale for values)
|
|
14625
|
-
this.yScale = this.d3
|
|
14626
|
-
.scaleLinear()
|
|
14627
|
-
.domain([0, this.d3.max(data, (d) => d.value) || 0])
|
|
14628
|
-
.nice()
|
|
14629
|
-
.range([this.height, 0]);
|
|
14630
|
-
}
|
|
14631
|
-
/**
|
|
14632
|
-
* Creates x and y axes with grid lines
|
|
14633
|
-
*/
|
|
14634
|
-
createAxes(options) {
|
|
14635
|
-
// Only create axes if they are enabled in options
|
|
14636
|
-
const showXAxis = options.showXAxis !== false;
|
|
14637
|
-
const showYAxis = options.showYAxis !== false;
|
|
14638
|
-
const showGrid = options.showGrid !== false;
|
|
14639
|
-
if (showXAxis) {
|
|
14640
|
-
// Create X axis
|
|
14641
|
-
this.xAxis = this.chart
|
|
14642
|
-
.append('g')
|
|
14643
|
-
.attr('class', 'axp-line-chart-axis-x')
|
|
14644
|
-
.attr('transform', `translate(0,${this.height})`)
|
|
14645
|
-
.call(this.d3.axisBottom(this.xScale));
|
|
14646
|
-
}
|
|
14647
|
-
if (showYAxis) {
|
|
14648
|
-
// Create Y axis
|
|
14649
|
-
this.yAxis = this.chart.append('g').attr('class', 'axp-line-chart-axis-y').call(this.d3.axisLeft(this.yScale));
|
|
14650
|
-
}
|
|
14651
|
-
if (showGrid) {
|
|
14652
|
-
// Add horizontal grid lines
|
|
14653
|
-
this.chart
|
|
14654
|
-
.append('g')
|
|
14655
|
-
.attr('class', 'axp-line-chart-grid')
|
|
14656
|
-
.call(this.d3
|
|
14657
|
-
.axisLeft(this.yScale)
|
|
14658
|
-
.tickSize(-this.width)
|
|
14659
|
-
.tickFormat(() => ''))
|
|
14660
|
-
.selectAll('.tick')
|
|
14661
|
-
.style('color', 'rgb(153 153 153 / 30%)'); // Add gray color to ticks
|
|
14662
|
-
}
|
|
14663
|
-
}
|
|
14664
|
-
/**
|
|
14665
|
-
* Renders the lines with animations
|
|
14666
|
-
*/
|
|
14667
|
-
renderLines(data) {
|
|
14668
|
-
// Get line options
|
|
14669
|
-
const lineWidth = this.lineWidth();
|
|
14670
|
-
const useSmooth = this.smoothLine();
|
|
14671
|
-
const shouldFill = this.fillArea();
|
|
14672
|
-
const fillOpacity = this.fillOpacity();
|
|
14673
|
-
// Create line generator
|
|
14674
|
-
const getX = (d) => {
|
|
14675
|
-
// Handle different x scale types
|
|
14676
|
-
if (this.xScale.bandwidth) {
|
|
14677
|
-
// band scale for categorical
|
|
14678
|
-
return this.xScale(d.xValue.toString()) + this.xScale.bandwidth() / 2;
|
|
14679
|
-
}
|
|
14680
|
-
else {
|
|
14681
|
-
// linear or time scale
|
|
14682
|
-
return this.xScale(d.xValue);
|
|
14683
|
-
}
|
|
14684
|
-
};
|
|
14685
|
-
// Define line generator function
|
|
14686
|
-
const lineGenerator = useSmooth
|
|
14687
|
-
? this.d3
|
|
14688
|
-
.line()
|
|
14689
|
-
.x(getX)
|
|
14690
|
-
.y((d) => this.yScale(d.value))
|
|
14691
|
-
.curve(this.d3.curveMonotoneX) // Smooth curve
|
|
14692
|
-
: this.d3
|
|
14693
|
-
.line()
|
|
14694
|
-
.x(getX)
|
|
14695
|
-
.y((d) => this.yScale(d.value))
|
|
14696
|
-
.curve(this.d3.curveLinear); // Straight lines
|
|
14697
|
-
// Define area generator if we should fill
|
|
14698
|
-
const areaGenerator = useSmooth
|
|
14699
|
-
? this.d3
|
|
14700
|
-
.area()
|
|
14701
|
-
.x(getX)
|
|
14702
|
-
.y0(this.height)
|
|
14703
|
-
.y1((d) => this.yScale(d.value))
|
|
14704
|
-
.curve(this.d3.curveMonotoneX)
|
|
14705
|
-
: this.d3
|
|
14706
|
-
.area()
|
|
14707
|
-
.x(getX)
|
|
14708
|
-
.y0(this.height)
|
|
14709
|
-
.y1((d) => this.yScale(d.value))
|
|
14710
|
-
.curve(this.d3.curveLinear);
|
|
14711
|
-
// Render each series
|
|
14712
|
-
data.forEach((seriesData, seriesIndex) => {
|
|
14713
|
-
// Skip empty series
|
|
14714
|
-
if (!seriesData.length)
|
|
14715
|
-
return;
|
|
14716
|
-
// Sort data within series by x value
|
|
14717
|
-
seriesData.sort((a, b) => {
|
|
14718
|
-
if (typeof a.xValue === 'number' && typeof b.xValue === 'number') {
|
|
14719
|
-
return a.xValue - b.xValue;
|
|
14720
|
-
}
|
|
14721
|
-
else {
|
|
14722
|
-
return a.xValue.toString().localeCompare(b.xValue.toString());
|
|
14723
|
-
}
|
|
14724
|
-
});
|
|
14725
|
-
// Get series name and color
|
|
14726
|
-
const seriesName = seriesData[0].seriesName || `Series ${seriesIndex + 1}`;
|
|
14727
|
-
// Get color for this series - prefer the color from data point, otherwise use palette
|
|
14728
|
-
const seriesColor = seriesData[0]?.color || AXPChartColors.getColor(seriesIndex);
|
|
14729
|
-
// Draw the area if fill is enabled
|
|
14730
|
-
if (shouldFill) {
|
|
14731
|
-
this.chart
|
|
14732
|
-
.append('path')
|
|
14733
|
-
.datum(seriesData)
|
|
14734
|
-
.attr('class', 'axp-line-chart-area')
|
|
14735
|
-
.attr('fill', seriesColor)
|
|
14736
|
-
.attr('fill-opacity', fillOpacity)
|
|
14737
|
-
.attr('d', areaGenerator);
|
|
14738
|
-
}
|
|
14739
|
-
// Draw the line with animation
|
|
14740
|
-
const path = this.chart
|
|
14741
|
-
.append('path')
|
|
14742
|
-
.datum(seriesData)
|
|
14743
|
-
.attr('class', 'axp-line-chart-line')
|
|
14744
|
-
.attr('stroke', seriesColor)
|
|
14745
|
-
.attr('stroke-width', lineWidth)
|
|
14746
|
-
.attr('fill', 'none') // Ensure no fill on the line
|
|
14747
|
-
.attr('d', lineGenerator);
|
|
14748
|
-
// Animate the line drawing
|
|
14749
|
-
const pathLength = path.node().getTotalLength();
|
|
14750
|
-
path
|
|
14751
|
-
.attr('stroke-dasharray', pathLength)
|
|
14752
|
-
.attr('stroke-dashoffset', pathLength)
|
|
14753
|
-
.transition()
|
|
14754
|
-
.duration(1000)
|
|
14755
|
-
.attr('stroke-dashoffset', 0)
|
|
14756
|
-
.ease(this.d3.easeQuadOut);
|
|
14757
|
-
// Add data points if enabled
|
|
14758
|
-
if (this.showPoints()) {
|
|
14759
|
-
this.chart
|
|
14760
|
-
.selectAll(`.axp-line-chart-point-${seriesIndex}`)
|
|
14761
|
-
.data(seriesData)
|
|
14762
|
-
.enter()
|
|
14763
|
-
.append('circle')
|
|
14764
|
-
.attr('class', `axp-line-chart-point axp-line-chart-point-${seriesIndex}`)
|
|
14765
|
-
.attr('cx', (d) => getX(d))
|
|
14766
|
-
.attr('cy', (d) => this.yScale(d.value))
|
|
14767
|
-
.attr('r', 0) // Start with radius 0 for animation
|
|
14768
|
-
.attr('fill', seriesColor)
|
|
14769
|
-
.attr('stroke', 'white')
|
|
14770
|
-
.attr('stroke-width', 1)
|
|
14771
|
-
.on('mouseenter', (event, d) => this.handlePointHover(event, d, seriesName))
|
|
14772
|
-
.on('mousemove', (event) => this.updateTooltipPosition(event))
|
|
14773
|
-
.on('mouseleave', () => this._tooltipVisible.set(false))
|
|
14774
|
-
.on('click', (event, d) => this.handlePointClick(event, d))
|
|
14775
|
-
.transition()
|
|
14776
|
-
.delay((d, i) => i * 50 + 300) // Stagger with delay after line animation
|
|
14777
|
-
.duration(300)
|
|
14778
|
-
.attr('r', this.pointRadius())
|
|
14779
|
-
.ease(this.d3.easeBackOut); // Bouncy animation
|
|
14780
|
-
}
|
|
14781
|
-
});
|
|
14782
|
-
}
|
|
14783
|
-
// Event handlers
|
|
14784
|
-
/**
|
|
14785
|
-
* Handles point hover event and shows tooltip
|
|
14786
|
-
*/
|
|
14787
|
-
handlePointHover(event, datum, seriesName) {
|
|
14788
|
-
if (this.options().showTooltip !== false) {
|
|
14789
|
-
const data = this.chartData();
|
|
14790
|
-
let index = 0;
|
|
14791
|
-
// Find index based on data structure
|
|
14792
|
-
if (Array.isArray(data)) {
|
|
14793
|
-
if (Array.isArray(data[0])) {
|
|
14794
|
-
// Multi-series data (array of arrays)
|
|
14795
|
-
const flatData = data.flat();
|
|
14796
|
-
index = flatData.findIndex((item) => item.id === datum.id);
|
|
14797
|
-
}
|
|
14798
|
-
else {
|
|
14799
|
-
// Single series (array of data points)
|
|
14800
|
-
index = data.findIndex((item) => item.id === datum.id);
|
|
14801
|
-
}
|
|
14802
|
-
}
|
|
14803
|
-
const color = datum.color || AXPChartColors.getColor(index);
|
|
14804
|
-
// Update tooltip data
|
|
14805
|
-
this._tooltipData.set({
|
|
14806
|
-
title: datum.label || seriesName,
|
|
14807
|
-
value: datum.value.toString(),
|
|
14808
|
-
percentage: '', // Not showing percentage for line charts
|
|
14809
|
-
color,
|
|
14810
|
-
});
|
|
14811
|
-
// Position tooltip near the point
|
|
14812
|
-
this.updateTooltipPosition(event);
|
|
14813
|
-
this._tooltipVisible.set(true);
|
|
14814
|
-
}
|
|
14815
|
-
}
|
|
14816
|
-
/**
|
|
14817
|
-
* Updates tooltip position based on mouse coordinates
|
|
14818
|
-
*/
|
|
14819
|
-
updateTooltipPosition(event) {
|
|
14820
|
-
const rect = event.target.getBoundingClientRect();
|
|
14821
|
-
const containerRect = this.chartContainerEl().nativeElement.getBoundingClientRect();
|
|
14822
|
-
// Position relative to the container
|
|
14823
|
-
const x = event.clientX - containerRect.left;
|
|
14824
|
-
const y = event.clientY - containerRect.top;
|
|
14825
|
-
this._tooltipPosition.set({ x, y });
|
|
14826
|
-
}
|
|
14827
|
-
/**
|
|
14828
|
-
* Handles point click event
|
|
14829
|
-
*/
|
|
14830
|
-
handlePointClick(event, datum) {
|
|
14831
|
-
// Emit click event with the data point
|
|
14832
|
-
this.pointClick.emit(datum);
|
|
14833
|
-
}
|
|
14834
|
-
/**
|
|
14835
|
-
* Shows a message when no data is available
|
|
14836
|
-
*/
|
|
14837
|
-
showNoDataMessage(containerElement) {
|
|
14838
|
-
const noDataDiv = this.d3.select(containerElement).append('div').attr('class', 'axp-line-chart-no-data-message');
|
|
14839
|
-
noDataDiv
|
|
14840
|
-
.append('div')
|
|
14841
|
-
.attr('class', 'axp-line-chart-no-data-icon')
|
|
14842
|
-
.html('<i class="fa-light fa-chart-line fa-3x"></i>');
|
|
14843
|
-
noDataDiv.append('div').attr('class', 'axp-line-chart-no-data-text').text('No data available');
|
|
14844
|
-
}
|
|
14845
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPLineChartWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
14846
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.0.3", type: AXPLineChartWidgetViewComponent, isStandalone: true, selector: "ng-component", outputs: { pointClick: "pointClick" }, viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"axp-line-chart\" #chartContainer>\n <!-- Shared tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"false\"\n ></ax-chart-tooltip>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px}.axp-line-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden}.axp-line-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}.axp-line-chart-line{fill:none;stroke-width:2px;stroke-linejoin:round;stroke-linecap:round;transition:stroke-width .3s ease}.axp-line-chart-line:hover{stroke-width:3px}.axp-line-chart-area{opacity:.2;transition:opacity .3s ease}.axp-line-chart-area:hover{opacity:.3}.axp-line-chart-point{cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1)}.axp-line-chart-point:hover{r:6;stroke-width:2;stroke:var(--ax-background-color, #fff)}.axp-line-chart-axis-x path,.axp-line-chart-axis-y path{stroke:var(--ax-border-color, #e0e0e0)}.axp-line-chart-axis-x line,.axp-line-chart-axis-y line,.axp-line-chart-grid line{stroke:var(--ax-border-color, #e0e0e0);stroke-dasharray:2,2;stroke-opacity:.5}.axp-line-chart-grid path{stroke-width:0}.axp-line-chart-axis-x text,.axp-line-chart-axis-y text{fill:var(--ax-text-muted, #666);font-size:clamp(8px,2vmin,12px)}.axp-line-chart-no-data-message{font-family:var(--ax-font-family, system-ui, sans-serif);display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:1rem;width:100%;height:100%}.axp-line-chart-no-data-icon{margin-bottom:.75rem;color:var(--ax-text-muted, #999)}.axp-line-chart-no-data-text{font-weight:600;color:var(--ax-text-color, #333)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AXPChartTooltipComponent, selector: "ax-chart-tooltip", inputs: ["data", "position", "visible", "showPercentage", "style"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
14847
|
-
}
|
|
14848
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPLineChartWidgetViewComponent, decorators: [{
|
|
14849
|
-
type: Component,
|
|
14850
|
-
args: [{ standalone: true, imports: [CommonModule, AXPChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"axp-line-chart\" #chartContainer>\n <!-- Shared tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"false\"\n ></ax-chart-tooltip>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px}.axp-line-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden}.axp-line-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}.axp-line-chart-line{fill:none;stroke-width:2px;stroke-linejoin:round;stroke-linecap:round;transition:stroke-width .3s ease}.axp-line-chart-line:hover{stroke-width:3px}.axp-line-chart-area{opacity:.2;transition:opacity .3s ease}.axp-line-chart-area:hover{opacity:.3}.axp-line-chart-point{cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1)}.axp-line-chart-point:hover{r:6;stroke-width:2;stroke:var(--ax-background-color, #fff)}.axp-line-chart-axis-x path,.axp-line-chart-axis-y path{stroke:var(--ax-border-color, #e0e0e0)}.axp-line-chart-axis-x line,.axp-line-chart-axis-y line,.axp-line-chart-grid line{stroke:var(--ax-border-color, #e0e0e0);stroke-dasharray:2,2;stroke-opacity:.5}.axp-line-chart-grid path{stroke-width:0}.axp-line-chart-axis-x text,.axp-line-chart-axis-y text{fill:var(--ax-text-muted, #666);font-size:clamp(8px,2vmin,12px)}.axp-line-chart-no-data-message{font-family:var(--ax-font-family, system-ui, sans-serif);display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:1rem;width:100%;height:100%}.axp-line-chart-no-data-icon{margin-bottom:.75rem;color:var(--ax-text-muted, #999)}.axp-line-chart-no-data-text{font-weight:600;color:var(--ax-text-color, #333)}\n"] }]
|
|
14851
|
-
}] });
|
|
14852
|
-
|
|
14853
|
-
var lineChartWidget_component = /*#__PURE__*/Object.freeze({
|
|
14854
|
-
__proto__: null,
|
|
14855
|
-
AXPLineChartWidgetViewComponent: AXPLineChartWidgetViewComponent
|
|
14856
|
-
});
|
|
14857
|
-
|
|
14858
|
-
const AXPLineChartWidget = {
|
|
14859
|
-
name: 'line-chart',
|
|
14860
|
-
title: 'Line Chart Widget',
|
|
14861
|
-
categories: [AXP_WIDGETS_CHART_CATEGORY],
|
|
14862
|
-
groups: [AXPWidgetGroupEnum.DashboardWidget],
|
|
14863
|
-
type: 'dashboard',
|
|
14864
|
-
icon: 'fa-light fa-chart-line',
|
|
14865
|
-
properties: [
|
|
14866
|
-
// ====== Layout & Dimensions ======
|
|
14867
|
-
{
|
|
14868
|
-
name: 'height',
|
|
14869
|
-
title: 'Height',
|
|
14870
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
14871
|
-
schema: {
|
|
14872
|
-
defaultValue: 300,
|
|
14873
|
-
dataType: 'number',
|
|
14874
|
-
interface: {
|
|
14875
|
-
name: 'height',
|
|
14876
|
-
path: 'options.height',
|
|
14877
|
-
type: AXPWidgetsCatalog.number,
|
|
14878
|
-
options: {
|
|
14879
|
-
minValue: 0,
|
|
14880
|
-
maxValue: 800,
|
|
14881
|
-
},
|
|
14882
|
-
},
|
|
14883
|
-
},
|
|
14884
|
-
visible: true,
|
|
14885
|
-
},
|
|
14886
|
-
{
|
|
14887
|
-
name: 'width',
|
|
14888
|
-
title: 'Width',
|
|
14889
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
14890
|
-
schema: {
|
|
14891
|
-
defaultValue: null,
|
|
14892
|
-
dataType: 'number',
|
|
14893
|
-
interface: {
|
|
14894
|
-
name: 'width',
|
|
14895
|
-
path: 'options.width',
|
|
14896
|
-
type: AXPWidgetsCatalog.number,
|
|
14897
|
-
options: {
|
|
14898
|
-
minValue: 0,
|
|
14899
|
-
maxValue: 1200,
|
|
14900
|
-
},
|
|
14901
|
-
},
|
|
14902
|
-
},
|
|
14903
|
-
visible: true,
|
|
14904
|
-
},
|
|
14905
|
-
// ====== Axis Settings ======
|
|
14906
|
-
{
|
|
14907
|
-
name: 'showXAxis',
|
|
14908
|
-
title: 'Show X Axis',
|
|
14909
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14910
|
-
schema: {
|
|
14911
|
-
defaultValue: true,
|
|
14912
|
-
dataType: 'boolean',
|
|
14913
|
-
interface: {
|
|
14914
|
-
name: 'showXAxis',
|
|
14915
|
-
path: 'options.showXAxis',
|
|
14916
|
-
type: AXPWidgetsCatalog.toggle,
|
|
14917
|
-
},
|
|
14918
|
-
},
|
|
14919
|
-
visible: true,
|
|
14920
|
-
},
|
|
14921
|
-
{
|
|
14922
|
-
name: 'showYAxis',
|
|
14923
|
-
title: 'Show Y Axis',
|
|
14924
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14925
|
-
schema: {
|
|
14926
|
-
defaultValue: true,
|
|
14927
|
-
dataType: 'boolean',
|
|
14928
|
-
interface: {
|
|
14929
|
-
name: 'showYAxis',
|
|
14930
|
-
path: 'options.showYAxis',
|
|
14931
|
-
type: AXPWidgetsCatalog.toggle,
|
|
14932
|
-
},
|
|
14933
|
-
},
|
|
14934
|
-
visible: true,
|
|
14935
|
-
},
|
|
14936
|
-
{
|
|
14937
|
-
name: 'showGrid',
|
|
14938
|
-
title: 'Show Grid Lines',
|
|
14939
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14940
|
-
schema: {
|
|
14941
|
-
defaultValue: true,
|
|
14942
|
-
dataType: 'boolean',
|
|
14943
|
-
interface: {
|
|
14944
|
-
name: 'showGrid',
|
|
14945
|
-
path: 'options.showGrid',
|
|
14946
|
-
type: AXPWidgetsCatalog.toggle,
|
|
14947
|
-
},
|
|
14948
|
-
},
|
|
14949
|
-
visible: true,
|
|
14950
|
-
},
|
|
14951
|
-
// ====== Tooltip Settings ======
|
|
14952
|
-
{
|
|
14953
|
-
name: 'showTooltip',
|
|
14954
|
-
title: 'Show Tooltip',
|
|
14955
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14956
|
-
schema: {
|
|
14957
|
-
defaultValue: true,
|
|
14958
|
-
dataType: 'boolean',
|
|
14959
|
-
interface: {
|
|
14960
|
-
name: 'showTooltip',
|
|
14961
|
-
path: 'options.showTooltip',
|
|
14962
|
-
type: AXPWidgetsCatalog.toggle,
|
|
14963
|
-
},
|
|
14964
|
-
},
|
|
14965
|
-
visible: true,
|
|
14966
|
-
},
|
|
14967
|
-
// ====== Line Appearance ======
|
|
14968
|
-
{
|
|
14969
|
-
name: 'lineWidth',
|
|
14970
|
-
title: 'Line Width',
|
|
14971
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14972
|
-
schema: {
|
|
14973
|
-
defaultValue: 2,
|
|
14974
|
-
dataType: 'number',
|
|
14975
|
-
interface: {
|
|
14976
|
-
name: 'lineWidth',
|
|
14977
|
-
path: 'options.lineWidth',
|
|
14978
|
-
type: AXPWidgetsCatalog.number,
|
|
14979
|
-
options: {
|
|
14980
|
-
placeholder: '1-10',
|
|
14981
|
-
minValue: 1,
|
|
14982
|
-
maxValue: 10,
|
|
14983
|
-
},
|
|
14984
|
-
},
|
|
14985
|
-
},
|
|
14986
|
-
visible: true,
|
|
14987
|
-
},
|
|
14988
|
-
{
|
|
14989
|
-
name: 'showPoints',
|
|
14990
|
-
title: 'Show Points',
|
|
14991
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
14992
|
-
schema: {
|
|
14993
|
-
defaultValue: true,
|
|
14994
|
-
dataType: 'boolean',
|
|
14995
|
-
interface: {
|
|
14996
|
-
name: 'showPoints',
|
|
14997
|
-
path: 'options.showPoints',
|
|
14998
|
-
type: AXPWidgetsCatalog.toggle,
|
|
14999
|
-
},
|
|
15000
|
-
},
|
|
15001
|
-
visible: true,
|
|
15002
|
-
},
|
|
15003
|
-
{
|
|
15004
|
-
name: 'pointRadius',
|
|
15005
|
-
title: 'Point Size',
|
|
15006
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15007
|
-
schema: {
|
|
15008
|
-
defaultValue: 4,
|
|
15009
|
-
dataType: 'number',
|
|
15010
|
-
interface: {
|
|
15011
|
-
name: 'pointRadius',
|
|
15012
|
-
path: 'options.pointRadius',
|
|
15013
|
-
type: AXPWidgetsCatalog.number,
|
|
15014
|
-
options: {
|
|
15015
|
-
placeholder: '1-10',
|
|
15016
|
-
minValue: 1,
|
|
15017
|
-
maxValue: 10,
|
|
15018
|
-
},
|
|
15019
|
-
},
|
|
15020
|
-
},
|
|
15021
|
-
visible: true,
|
|
15022
|
-
},
|
|
15023
|
-
{
|
|
15024
|
-
name: 'smoothLine',
|
|
15025
|
-
title: 'Smooth Line',
|
|
15026
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15027
|
-
schema: {
|
|
15028
|
-
defaultValue: true,
|
|
15029
|
-
dataType: 'boolean',
|
|
15030
|
-
interface: {
|
|
15031
|
-
name: 'smoothLine',
|
|
15032
|
-
path: 'options.smoothLine',
|
|
15033
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15034
|
-
},
|
|
15035
|
-
},
|
|
15036
|
-
visible: true,
|
|
15037
|
-
},
|
|
15038
|
-
{
|
|
15039
|
-
name: 'fillArea',
|
|
15040
|
-
title: 'Fill Area',
|
|
15041
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15042
|
-
schema: {
|
|
15043
|
-
defaultValue: false,
|
|
15044
|
-
dataType: 'boolean',
|
|
15045
|
-
interface: {
|
|
15046
|
-
name: 'fillArea',
|
|
15047
|
-
path: 'options.fillArea',
|
|
15048
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15049
|
-
},
|
|
15050
|
-
},
|
|
15051
|
-
visible: true,
|
|
15052
|
-
},
|
|
15053
|
-
{
|
|
15054
|
-
name: 'fillOpacity',
|
|
15055
|
-
title: 'Fill Opacity',
|
|
15056
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15057
|
-
schema: {
|
|
15058
|
-
defaultValue: 10,
|
|
15059
|
-
dataType: 'number',
|
|
15060
|
-
interface: {
|
|
15061
|
-
name: 'fillOpacity',
|
|
15062
|
-
path: 'options.fillOpacity',
|
|
15063
|
-
type: AXPWidgetsCatalog.number,
|
|
15064
|
-
options: {
|
|
15065
|
-
placeholder: '0-100',
|
|
15066
|
-
minValue: 0,
|
|
15067
|
-
maxValue: 100,
|
|
15068
|
-
},
|
|
15069
|
-
},
|
|
15070
|
-
},
|
|
15071
|
-
visible: true,
|
|
15072
|
-
},
|
|
15073
|
-
],
|
|
15074
|
-
components: {
|
|
15075
|
-
view: {
|
|
15076
|
-
component: () => Promise.resolve().then(function () { return lineChartWidget_component; }).then((c) => c.AXPLineChartWidgetViewComponent),
|
|
15077
|
-
},
|
|
15078
|
-
},
|
|
15079
|
-
meta: {
|
|
15080
|
-
dimensions: {
|
|
15081
|
-
width: 5,
|
|
15082
|
-
height: 6,
|
|
15083
|
-
minWidth: 2,
|
|
15084
|
-
minHeight: 2,
|
|
15085
|
-
maxWidth: 6,
|
|
15086
|
-
maxHeight: 7,
|
|
15087
|
-
},
|
|
15088
|
-
},
|
|
15089
|
-
};
|
|
15090
|
-
|
|
15091
|
-
/**
|
|
15092
|
-
* Notification Widget Component
|
|
15093
|
-
* Displays notifications in a card with tabs
|
|
15094
|
-
*/
|
|
15095
|
-
class AXPNotificationWidgetViewComponent extends AXPValueWidgetComponent {
|
|
15096
|
-
constructor() {
|
|
15097
|
-
super(...arguments);
|
|
15098
|
-
// Outputs
|
|
15099
|
-
this.notificationClick = output();
|
|
15100
|
-
this.markAsRead = output();
|
|
15101
|
-
// Dependencies
|
|
15102
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
15103
|
-
this.datePipe = inject(DatePipe);
|
|
15104
|
-
// State
|
|
15105
|
-
this.activeTab = signal('new');
|
|
15106
|
-
// Configuration
|
|
15107
|
-
this.maxItems = computed(() => this.options()?.maxItems ?? 10);
|
|
15108
|
-
this.showAvatar = computed(() => this.options()?.showAvatar ?? true);
|
|
15109
|
-
this.showDate = computed(() => this.options()?.showDate ?? true);
|
|
15110
|
-
// Computed data
|
|
15111
|
-
this.notificationItems = computed(() => {
|
|
15112
|
-
const value = this.getValue();
|
|
15113
|
-
if (!value?.data?.length)
|
|
15114
|
-
return [];
|
|
15115
|
-
// Filter by active tab
|
|
15116
|
-
const filtered = this.activeTab() === 'new' ? value.data.filter((n) => !n.readAt) : value.data;
|
|
15117
|
-
return filtered.slice(0, this.maxItems());
|
|
15118
|
-
});
|
|
15119
|
-
}
|
|
15120
|
-
/**
|
|
15121
|
-
* Get the count of new messages for the badge
|
|
15122
|
-
*/
|
|
15123
|
-
getNewMessageCount() {
|
|
15124
|
-
const value = this.getValue();
|
|
15125
|
-
if (!value?.data?.length)
|
|
15126
|
-
return 0;
|
|
15127
|
-
return value.data.filter((n) => !n.readAt).length;
|
|
15128
|
-
}
|
|
15129
|
-
/**
|
|
15130
|
-
* Handle tab change event from ax-tabs component
|
|
15131
|
-
* @param index The index of the tab that was activated
|
|
15132
|
-
*/
|
|
15133
|
-
handleTabChange(data) {
|
|
15134
|
-
const index = data.index;
|
|
15135
|
-
// Map index to tab name: 0 = 'new', 1 = 'all'
|
|
15136
|
-
const tabName = index === 0 ? 'new' : 'all';
|
|
15137
|
-
this.onTabChange(tabName);
|
|
15138
|
-
}
|
|
15139
|
-
/**
|
|
15140
|
-
* Mark all notifications as read
|
|
15141
|
-
*/
|
|
15142
|
-
markAllAsRead() {
|
|
15143
|
-
const value = this.getValue();
|
|
15144
|
-
if (!value?.data?.length)
|
|
15145
|
-
return;
|
|
15146
|
-
const now = new Date();
|
|
15147
|
-
const updatedNotifications = value.data.map((n) => {
|
|
15148
|
-
if (n.readAt)
|
|
15149
|
-
return n;
|
|
15150
|
-
return { ...n, readAt: now };
|
|
15151
|
-
});
|
|
15152
|
-
this.setValue({
|
|
15153
|
-
...value,
|
|
15154
|
-
data: updatedNotifications,
|
|
15155
|
-
});
|
|
15156
|
-
this.cdr.detectChanges();
|
|
15157
|
-
}
|
|
15158
|
-
/**
|
|
15159
|
-
* Change the active tab
|
|
15160
|
-
*/
|
|
15161
|
-
onTabChange(tabName) {
|
|
15162
|
-
this.activeTab.set(tabName);
|
|
15163
|
-
this.cdr.detectChanges();
|
|
15164
|
-
}
|
|
15165
|
-
/**
|
|
15166
|
-
* Handle notification click event
|
|
15167
|
-
*/
|
|
15168
|
-
onNotificationClick(notification) {
|
|
15169
|
-
this.markAsReadIfNeeded(notification);
|
|
15170
|
-
this.notificationClick.emit(notification);
|
|
15171
|
-
}
|
|
15172
|
-
/**
|
|
15173
|
-
* Format the timestamp in a user-friendly way
|
|
15174
|
-
*/
|
|
15175
|
-
formatTime(date) {
|
|
15176
|
-
if (!date)
|
|
15177
|
-
return '';
|
|
15178
|
-
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
|
15179
|
-
const diffDays = this.getDaysDifference(dateObj);
|
|
15180
|
-
// Format based on how recent the date is
|
|
15181
|
-
if (diffDays === 0)
|
|
15182
|
-
return this.datePipe.transform(dateObj, 'h:mm a') || ''; // Today
|
|
15183
|
-
if (diffDays === 1)
|
|
15184
|
-
return 'Yesterday';
|
|
15185
|
-
if (diffDays < 7)
|
|
15186
|
-
return this.datePipe.transform(dateObj, 'EEE') || ''; // Day of week
|
|
15187
|
-
return this.datePipe.transform(dateObj, 'MM/dd/yyyy') || ''; // Older date
|
|
15188
|
-
}
|
|
15189
|
-
/**
|
|
15190
|
-
* Mark notification as read if needed
|
|
15191
|
-
*/
|
|
15192
|
-
markAsReadIfNeeded(notification) {
|
|
15193
|
-
// Only mark as read if not already read
|
|
15194
|
-
if (notification.readAt)
|
|
15195
|
-
return;
|
|
15196
|
-
const updatedNotification = {
|
|
15197
|
-
...notification,
|
|
15198
|
-
readAt: new Date(),
|
|
15199
|
-
};
|
|
15200
|
-
// Update the model
|
|
15201
|
-
const value = this.getValue();
|
|
15202
|
-
if (!value?.data)
|
|
15203
|
-
return;
|
|
15204
|
-
const updatedNotifications = value.data.map((n) => (n.id === notification.id ? updatedNotification : n));
|
|
15205
|
-
this.setValue({
|
|
15206
|
-
...value,
|
|
15207
|
-
data: updatedNotifications,
|
|
15208
|
-
});
|
|
15209
|
-
// Notify about the change
|
|
15210
|
-
this.markAsRead.emit(updatedNotification);
|
|
15211
|
-
}
|
|
15212
|
-
/**
|
|
15213
|
-
* Calculate days difference from now
|
|
15214
|
-
*/
|
|
15215
|
-
getDaysDifference(date) {
|
|
15216
|
-
const now = new Date();
|
|
15217
|
-
const diffMs = now.getTime() - date.getTime();
|
|
15218
|
-
return Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
15219
|
-
}
|
|
15220
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPNotificationWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
15221
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: AXPNotificationWidgetViewComponent, isStandalone: true, selector: "ng-component", outputs: { notificationClick: "notificationClick", markAsRead: "markAsRead" }, providers: [DatePipe], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-p-4 ax-size-full\">\n <ax-tabs\n class=\"ax-bg-light-start ax-border-b ax-border-default\"\n [fitParent]=\"true\"\n location=\"bottom\"\n (onActiveTabChanged)=\"handleTabChange($event)\"\n >\n <ax-tab-item [text]=\"('widget.notification.new' | translate | async) ?? 'New'\" class=\"ax-font-medium\">\n <ax-suffix>\n @if (getNewMessageCount() > 0) {\n <ax-badge color=\"primary\" [text]=\"getNewMessageCount().toString()\" size=\"sm\" class=\"ax-ml-1\"></ax-badge>\n }\n </ax-suffix>\n </ax-tab-item>\n <ax-tab-item [text]=\"('widget.notification.all' | translate | async) ?? 'All'\" class=\"ax-font-medium\"></ax-tab-item>\n </ax-tabs>\n <div class=\"ax-space-y-4 ax-mt-4 ax-px-2\">\n @for (item of notificationItems(); track item.id) {\n <ng-container [ngTemplateOutlet]=\"chatItemTemplateRef\" [ngTemplateOutletContext]=\"{ $implicit: item }\">\n </ng-container>\n } @empty {\n <div class=\"ax-flex ax-flex-col ax-items-center ax-justify-center ax-py-12 ax-px-4 ax-text-gray-400\">\n <ax-icon class=\"ax-text-4xl ax-mb-3 ax-text-gray-300\">\n <i class=\"fa-light fa-bell-slash\"></i>\n </ax-icon>\n <p class=\"ax-text-center\">{{ 'widget.notification.noNotifications' | translate | async }}</p>\n </div>\n }\n </div>\n</div>\n\n<ng-template #chatItemTemplateRef let-data>\n <div class=\"ax-flex ax-gap-3\">\n @if(showAvatar()){\n <div class=\"ax-rounded-full ax-size-10\">\n <ax-avatar shape=\"rounded\" class=\"ax-shrink-0\" [size]=\"40\">\n @if(data.user?.image){\n <ax-image\n [src]=\"data.user.image\"\n [alt]=\"data.user?.name || ('widget.notification.user' | translate | async)\"\n ></ax-image>\n } @else {\n <ax-icon>\n <i class=\"fa-light fa-user\"></i>\n </ax-icon>\n }\n </ax-avatar>\n </div>\n }\n <div class=\"ax-overflow-hidden ax-grow ax-text-start\">\n <h6 class=\"ax-pb-2 ax-font-semibold ax-truncate\">{{ data.user?.name || data.title }}</h6>\n <p class=\"ax-text-xs ax-truncate\">{{ data.body }}</p>\n </div>\n @if(showDate()){\n <div class=\"ax-text-xs ax-shrink-0\">\n <span>\n {{ formatTime(data.createdAt) }}\n </span>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: AXTabsModule }, { kind: "component", type: i4$1.AXTabsComponent, selector: "ax-tabs", inputs: ["look", "location", "fitParent", "minWidth", "content"], outputs: ["onActiveTabChanged"] }, { kind: "component", type: i4$1.AXTabItemComponent, selector: "ax-tab-item", inputs: ["disabled", "text", "key", "headerTemplate", "active"], outputs: ["disabledChange", "onClick", "onBlur", "onFocus", "activeChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i1$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXAvatarModule }, { kind: "component", type: i5$4.AXAvatarComponent, selector: "ax-avatar", inputs: ["color", "size", "shape", "look"], outputs: ["sizeChange"] }, { kind: "ngmodule", type: AXImageModule }, { kind: "component", type: i2$7.AXImageComponent, selector: "ax-image", inputs: ["width", "height", "overlayMode", "src", "alt", "priority", "lazy"], outputs: ["onLoad", "onError"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i5$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15222
|
-
}
|
|
15223
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPNotificationWidgetViewComponent, decorators: [{
|
|
15224
|
-
type: Component,
|
|
15225
|
-
args: [{ standalone: true, imports: [
|
|
15226
|
-
CommonModule,
|
|
15227
|
-
AXTabsModule,
|
|
15228
|
-
AXDecoratorModule,
|
|
15229
|
-
AXButtonModule,
|
|
15230
|
-
AXBadgeModule,
|
|
15231
|
-
AXAvatarModule,
|
|
15232
|
-
AXImageModule,
|
|
15233
|
-
AXTranslationModule,
|
|
15234
|
-
], providers: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-p-4 ax-size-full\">\n <ax-tabs\n class=\"ax-bg-light-start ax-border-b ax-border-default\"\n [fitParent]=\"true\"\n location=\"bottom\"\n (onActiveTabChanged)=\"handleTabChange($event)\"\n >\n <ax-tab-item [text]=\"('widget.notification.new' | translate | async) ?? 'New'\" class=\"ax-font-medium\">\n <ax-suffix>\n @if (getNewMessageCount() > 0) {\n <ax-badge color=\"primary\" [text]=\"getNewMessageCount().toString()\" size=\"sm\" class=\"ax-ml-1\"></ax-badge>\n }\n </ax-suffix>\n </ax-tab-item>\n <ax-tab-item [text]=\"('widget.notification.all' | translate | async) ?? 'All'\" class=\"ax-font-medium\"></ax-tab-item>\n </ax-tabs>\n <div class=\"ax-space-y-4 ax-mt-4 ax-px-2\">\n @for (item of notificationItems(); track item.id) {\n <ng-container [ngTemplateOutlet]=\"chatItemTemplateRef\" [ngTemplateOutletContext]=\"{ $implicit: item }\">\n </ng-container>\n } @empty {\n <div class=\"ax-flex ax-flex-col ax-items-center ax-justify-center ax-py-12 ax-px-4 ax-text-gray-400\">\n <ax-icon class=\"ax-text-4xl ax-mb-3 ax-text-gray-300\">\n <i class=\"fa-light fa-bell-slash\"></i>\n </ax-icon>\n <p class=\"ax-text-center\">{{ 'widget.notification.noNotifications' | translate | async }}</p>\n </div>\n }\n </div>\n</div>\n\n<ng-template #chatItemTemplateRef let-data>\n <div class=\"ax-flex ax-gap-3\">\n @if(showAvatar()){\n <div class=\"ax-rounded-full ax-size-10\">\n <ax-avatar shape=\"rounded\" class=\"ax-shrink-0\" [size]=\"40\">\n @if(data.user?.image){\n <ax-image\n [src]=\"data.user.image\"\n [alt]=\"data.user?.name || ('widget.notification.user' | translate | async)\"\n ></ax-image>\n } @else {\n <ax-icon>\n <i class=\"fa-light fa-user\"></i>\n </ax-icon>\n }\n </ax-avatar>\n </div>\n }\n <div class=\"ax-overflow-hidden ax-grow ax-text-start\">\n <h6 class=\"ax-pb-2 ax-font-semibold ax-truncate\">{{ data.user?.name || data.title }}</h6>\n <p class=\"ax-text-xs ax-truncate\">{{ data.body }}</p>\n </div>\n @if(showDate()){\n <div class=\"ax-text-xs ax-shrink-0\">\n <span>\n {{ formatTime(data.createdAt) }}\n </span>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block;width:100%;height:100%}\n"] }]
|
|
15235
|
-
}] });
|
|
15236
|
-
|
|
15237
|
-
var notificationWidget_component = /*#__PURE__*/Object.freeze({
|
|
15238
|
-
__proto__: null,
|
|
15239
|
-
AXPNotificationWidgetViewComponent: AXPNotificationWidgetViewComponent
|
|
15240
|
-
});
|
|
15241
|
-
|
|
15242
|
-
/**
|
|
15243
|
-
* Configuration for the Notification Widget
|
|
15244
|
-
*/
|
|
15245
|
-
const AXPNotificationWidget = {
|
|
15246
|
-
name: 'notification',
|
|
15247
|
-
title: 'Notification Widget',
|
|
15248
|
-
categories: [AXP_WIDGETS_UTILITY_CATEGORY],
|
|
15249
|
-
groups: [AXPWidgetGroupEnum.DashboardWidget],
|
|
15250
|
-
type: 'dashboard',
|
|
15251
|
-
description: 'Displays notifications in a widget format',
|
|
15252
|
-
icon: 'fa-regular fa-bell',
|
|
15253
|
-
properties: [
|
|
15254
|
-
cloneProperty(AXP_DATA_PATH_PROPERTY, { visible: false }),
|
|
15255
|
-
{
|
|
15256
|
-
name: 'maxItems',
|
|
15257
|
-
title: 'Max Items',
|
|
15258
|
-
description: 'Maximum number of notification items to display',
|
|
15259
|
-
group: AXP_STYLING_PROPERTY_GROUP,
|
|
15260
|
-
schema: {
|
|
15261
|
-
defaultValue: 10,
|
|
15262
|
-
dataType: 'number',
|
|
15263
|
-
interface: {
|
|
15264
|
-
name: 'maxItems',
|
|
15265
|
-
path: 'options.maxItems',
|
|
15266
|
-
type: AXPWidgetsCatalog.number,
|
|
15267
|
-
options: {
|
|
15268
|
-
minValue: 1,
|
|
15269
|
-
maxValue: 100,
|
|
15270
|
-
},
|
|
15271
|
-
},
|
|
15272
|
-
},
|
|
15273
|
-
visible: true,
|
|
15274
|
-
},
|
|
15275
|
-
{
|
|
15276
|
-
name: 'showAvatar',
|
|
15277
|
-
title: 'Show Avatar',
|
|
15278
|
-
description: 'Whether to show avatars in notifications',
|
|
15279
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15280
|
-
schema: {
|
|
15281
|
-
defaultValue: true,
|
|
15282
|
-
dataType: 'boolean',
|
|
15283
|
-
interface: {
|
|
15284
|
-
name: 'showAvatar',
|
|
15285
|
-
path: 'options.showAvatar',
|
|
15286
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15287
|
-
},
|
|
15288
|
-
},
|
|
15289
|
-
visible: true,
|
|
15290
|
-
},
|
|
15291
|
-
{
|
|
15292
|
-
name: 'showDate',
|
|
15293
|
-
title: 'Show Date',
|
|
15294
|
-
description: 'Whether to show date in notifications',
|
|
15295
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15296
|
-
schema: {
|
|
15297
|
-
defaultValue: true,
|
|
15298
|
-
dataType: 'boolean',
|
|
15299
|
-
interface: {
|
|
15300
|
-
name: 'showDate',
|
|
15301
|
-
path: 'options.showDate',
|
|
15302
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15303
|
-
},
|
|
15304
|
-
},
|
|
15305
|
-
visible: true,
|
|
15306
|
-
},
|
|
15307
|
-
],
|
|
15308
|
-
components: {
|
|
15309
|
-
view: {
|
|
15310
|
-
component: () => Promise.resolve().then(function () { return notificationWidget_component; }).then((c) => c.AXPNotificationWidgetViewComponent),
|
|
15311
|
-
},
|
|
15312
|
-
},
|
|
15313
|
-
meta: {
|
|
15314
|
-
dimensions: {
|
|
15315
|
-
width: 3,
|
|
15316
|
-
height: 5,
|
|
15317
|
-
minWidth: 2,
|
|
15318
|
-
minHeight: 4,
|
|
15319
|
-
maxWidth: 4,
|
|
15320
|
-
maxHeight: 7,
|
|
15321
|
-
},
|
|
15322
|
-
},
|
|
15323
|
-
};
|
|
15324
|
-
|
|
15325
|
-
class AXPStickyNoteWidgetViewComponent extends AXPValueWidgetComponent {
|
|
15326
|
-
constructor() {
|
|
15327
|
-
super(...arguments);
|
|
15328
|
-
this.isEditing = signal(false);
|
|
15329
|
-
this.wysiwyg = viewChild('wysiwyg');
|
|
15330
|
-
this.value = computed(() => this.getValue());
|
|
15331
|
-
this.date = computed(() => this.options()?.date ?? new Date());
|
|
15332
|
-
this.bgColor = computed(() => this.options()?.backgroundColor ?? '#FFF8B8');
|
|
15333
|
-
this.color = signal('#333333');
|
|
15334
|
-
this.el = inject(ElementRef);
|
|
15335
|
-
// Modern color palette with pastel and vibrant options
|
|
15336
|
-
this.colorPresets = [
|
|
15337
|
-
'#FFF8B8', // Soft yellow
|
|
15338
|
-
'#FFD8E6', // Soft pink
|
|
15339
|
-
'#D1F0FF', // Soft blue
|
|
15340
|
-
'#E2FFD1', // Soft green
|
|
15341
|
-
'#FFE8D1', // Soft orange
|
|
15342
|
-
'#F0D1FF', // Soft purple
|
|
15343
|
-
'#FFCDD2', // Soft red
|
|
15344
|
-
'#D1FFF0', // Soft teal
|
|
15345
|
-
'#F5F5F5', // Light gray
|
|
15346
|
-
'#FFFFFF', // White
|
|
15347
|
-
];
|
|
15348
|
-
}
|
|
15349
|
-
// Handle clicks outside the component to cancel editing
|
|
15350
|
-
handleClickOutside(event) {
|
|
15351
|
-
const clickedInside = this.el.nativeElement.contains(event.target);
|
|
15352
|
-
if (!clickedInside && this.isEditing()) {
|
|
15353
|
-
this.saveChanges();
|
|
15354
|
-
}
|
|
15355
|
-
}
|
|
15356
|
-
// Handle double-click to activate editing
|
|
15357
|
-
activateEdit() {
|
|
15358
|
-
//TODO FOCUS WYSIWYG
|
|
15359
|
-
this.wysiwyg()?.focus();
|
|
15360
|
-
this.isEditing.set(true);
|
|
15361
|
-
}
|
|
15362
|
-
// Save changes and exit edit mode
|
|
15363
|
-
saveChanges() {
|
|
15364
|
-
this.isEditing.set(false);
|
|
15365
|
-
}
|
|
15366
|
-
setColor(color) {
|
|
15367
|
-
this.setOptions({ backgroundColor: color });
|
|
15368
|
-
}
|
|
15369
|
-
valueChange(event) {
|
|
15370
|
-
if (event.isUserInteraction) {
|
|
15371
|
-
this.setValue(event.value);
|
|
15372
|
-
}
|
|
15373
|
-
}
|
|
15374
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPStickyNoteWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
15375
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: AXPStickyNoteWidgetViewComponent, isStandalone: true, selector: "ng-component", host: { listeners: { "document:click": "handleClickOutside($event)" } }, providers: [
|
|
15376
|
-
{
|
|
15377
|
-
provide: AXGridLayoutWidgetComponent,
|
|
15378
|
-
useExisting: AXPStickyNoteWidgetViewComponent,
|
|
15379
|
-
},
|
|
15380
|
-
], viewQueries: [{ propertyName: "wysiwyg", first: true, predicate: ["wysiwyg"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n class=\"sticky-note-container ax-size-full ax-rounded-lg ax-flex ax-flex-col ax-p-4 ax-shadow-md ax-transition-all ax-duration-300 hover:ax-shadow-lg\"\n [style.background-color]=\"bgColor()\"\n [style.color]=\"color()\"\n [class.ax-shadow-lg]=\"isEditing()\"\n [class.ax-scale-[1.02]]=\"isEditing()\"\n (dblclick)=\"activateEdit()\"\n>\n <!-- Header with timestamp -->\n <div class=\"ax-flex ax-justify-between ax-items-center ax-mb-3\">\n <div class=\"ax-text-xs ax-opacity-70 ax-font-medium ax-flex ax-items-center ax-gap-1\">\n <i class=\"fa-regular fa-clock ax-text-[0.65rem]\"></i>\n {{ date() | format : 'datetime' : 'dd MMM, YY HH:mm' | async }}\n </div>\n </div>\n\n <!-- Content area -->\n <div class=\"ax-flex-1 ax-overflow-auto\">\n <ax-wysiwyg-container\n #wysiwyg\n [class]=\"isEditing() ? 'ax-pointer-events-auto ax-cursor-text' : 'ax-pointer-events-none !ax-cursor-pointer'\"\n class=\"ax-h-full\"\n placeHolder=\"start writing with double click...\"\n look=\"none\"\n (onValueChanged)=\"valueChange($event)\"\n [ngModel]=\"value()\"\n >\n <ax-wysiwyg-view class=\"!ax-size-full ax-border-b-0\"></ax-wysiwyg-view>\n </ax-wysiwyg-container>\n </div>\n\n <!-- Footer with color selector and save button -->\n @if (isEditing()) {\n <div class=\"ax-absolute ax-bottom-2 ax-left-2 ax-right-2 ax-flex ax-flex-wrap ax-gap-1 ax-text-xs\">\n <!-- Color selection bar when in edit mode -->\n <div class=\"ax-flex ax-w-full ax-mt-2 ax-border ax-border-gray-200 ax-overflow-hidden ax-rounded-md\">\n @for (preset of colorPresets; track preset) {\n <div\n class=\"ax-h-6 ax-flex-1 ax-cursor-pointer ax-transition-all ax-duration-200 ax-border-r ax-border-gray-200 last:ax-border-r-0 hover:ax-brightness-95\"\n [style.background-color]=\"preset\"\n [class.ax-ring-inset]=\"bgColor() === preset\"\n [class.ax-ring-2]=\"bgColor() === preset\"\n [class.ax-ring-primary]=\"bgColor() === preset\"\n (click)=\"setColor(preset)\"\n ></div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep ax-wysiwyg-view{color:#2e2e2e!important}\n"], dependencies: [{ kind: "ngmodule", type: AXWysiwygModule }, { kind: "component", type: i2$3.AXWysiwygContainerComponent, selector: "ax-wysiwyg-container", inputs: ["look", "placeHolder"], outputs: ["onValueChanged"] }, { kind: "component", type: i2$3.AXWysiwygViewComponent, selector: "ax-wysiwyg-view", inputs: ["class"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXToolBarModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXDateTimeModule }, { kind: "ngmodule", type: AXFormatModule }, { kind: "pipe", type: i2$9.AXFormatPipe, name: "format" }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: AXColorBoxModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15381
|
-
}
|
|
15382
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPStickyNoteWidgetViewComponent, decorators: [{
|
|
15383
|
-
type: Component,
|
|
15384
|
-
args: [{ standalone: true, imports: [
|
|
15385
|
-
AXWysiwygModule,
|
|
15386
|
-
AXDecoratorModule,
|
|
15387
|
-
AXToolBarModule,
|
|
15388
|
-
FormsModule,
|
|
15389
|
-
AXDateTimeModule,
|
|
15390
|
-
AXFormatModule,
|
|
15391
|
-
AXPopoverModule,
|
|
15392
|
-
CommonModule,
|
|
15393
|
-
FormsModule,
|
|
15394
|
-
AXColorBoxModule,
|
|
15395
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
15396
|
-
{
|
|
15397
|
-
provide: AXGridLayoutWidgetComponent,
|
|
15398
|
-
useExisting: AXPStickyNoteWidgetViewComponent,
|
|
15399
|
-
},
|
|
15400
|
-
], template: "<div\n class=\"sticky-note-container ax-size-full ax-rounded-lg ax-flex ax-flex-col ax-p-4 ax-shadow-md ax-transition-all ax-duration-300 hover:ax-shadow-lg\"\n [style.background-color]=\"bgColor()\"\n [style.color]=\"color()\"\n [class.ax-shadow-lg]=\"isEditing()\"\n [class.ax-scale-[1.02]]=\"isEditing()\"\n (dblclick)=\"activateEdit()\"\n>\n <!-- Header with timestamp -->\n <div class=\"ax-flex ax-justify-between ax-items-center ax-mb-3\">\n <div class=\"ax-text-xs ax-opacity-70 ax-font-medium ax-flex ax-items-center ax-gap-1\">\n <i class=\"fa-regular fa-clock ax-text-[0.65rem]\"></i>\n {{ date() | format : 'datetime' : 'dd MMM, YY HH:mm' | async }}\n </div>\n </div>\n\n <!-- Content area -->\n <div class=\"ax-flex-1 ax-overflow-auto\">\n <ax-wysiwyg-container\n #wysiwyg\n [class]=\"isEditing() ? 'ax-pointer-events-auto ax-cursor-text' : 'ax-pointer-events-none !ax-cursor-pointer'\"\n class=\"ax-h-full\"\n placeHolder=\"start writing with double click...\"\n look=\"none\"\n (onValueChanged)=\"valueChange($event)\"\n [ngModel]=\"value()\"\n >\n <ax-wysiwyg-view class=\"!ax-size-full ax-border-b-0\"></ax-wysiwyg-view>\n </ax-wysiwyg-container>\n </div>\n\n <!-- Footer with color selector and save button -->\n @if (isEditing()) {\n <div class=\"ax-absolute ax-bottom-2 ax-left-2 ax-right-2 ax-flex ax-flex-wrap ax-gap-1 ax-text-xs\">\n <!-- Color selection bar when in edit mode -->\n <div class=\"ax-flex ax-w-full ax-mt-2 ax-border ax-border-gray-200 ax-overflow-hidden ax-rounded-md\">\n @for (preset of colorPresets; track preset) {\n <div\n class=\"ax-h-6 ax-flex-1 ax-cursor-pointer ax-transition-all ax-duration-200 ax-border-r ax-border-gray-200 last:ax-border-r-0 hover:ax-brightness-95\"\n [style.background-color]=\"preset\"\n [class.ax-ring-inset]=\"bgColor() === preset\"\n [class.ax-ring-2]=\"bgColor() === preset\"\n [class.ax-ring-primary]=\"bgColor() === preset\"\n (click)=\"setColor(preset)\"\n ></div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep ax-wysiwyg-view{color:#2e2e2e!important}\n"] }]
|
|
15401
|
-
}], propDecorators: { handleClickOutside: [{
|
|
15402
|
-
type: HostListener,
|
|
15403
|
-
args: ['document:click', ['$event']]
|
|
15404
|
-
}] } });
|
|
15405
|
-
|
|
15406
|
-
var stickyNoteWidget_component = /*#__PURE__*/Object.freeze({
|
|
15407
|
-
__proto__: null,
|
|
15408
|
-
AXPStickyNoteWidgetViewComponent: AXPStickyNoteWidgetViewComponent
|
|
15409
|
-
});
|
|
15410
|
-
|
|
15411
|
-
const AXPStickyNoteWidget = {
|
|
15412
|
-
name: 'sticky-note',
|
|
15413
|
-
title: 'Sticky Note Widget',
|
|
15414
|
-
categories: AXP_WIDGETS_UTILITY_CATEGORY,
|
|
15415
|
-
groups: [AXPWidgetGroupEnum.DashboardWidget],
|
|
15416
|
-
type: 'dashboard',
|
|
15417
|
-
icon: 'fa-light fa-sticky-note',
|
|
15418
|
-
properties: [AXP_DATA_PATH_PROPERTY, AXP_BG_COLOR_PROPERTY, plainTextDefaultProperty()],
|
|
15419
|
-
components: {
|
|
15420
|
-
view: {
|
|
15421
|
-
component: () => Promise.resolve().then(function () { return stickyNoteWidget_component; }).then((c) => c.AXPStickyNoteWidgetViewComponent),
|
|
15422
|
-
},
|
|
15423
|
-
},
|
|
15424
|
-
meta: {
|
|
15425
|
-
dimensions: {
|
|
15426
|
-
width: 2,
|
|
15427
|
-
height: 3,
|
|
15428
|
-
minWidth: 2,
|
|
15429
|
-
minHeight: 2,
|
|
15430
|
-
maxWidth: 4,
|
|
15431
|
-
maxHeight: 4,
|
|
15432
|
-
},
|
|
15433
|
-
},
|
|
15434
|
-
};
|
|
15435
|
-
|
|
15436
|
-
/**
|
|
15437
|
-
* Task List Widget Component
|
|
15438
|
-
* Displays a list of tasks with checkboxes and filtering options
|
|
15439
|
-
*/
|
|
15440
|
-
class AXPTaskListWidgetViewComponent extends AXPValueWidgetComponent {
|
|
15441
|
-
constructor() {
|
|
15442
|
-
super(...arguments);
|
|
15443
|
-
// Outputs
|
|
15444
|
-
this.taskClick = output();
|
|
15445
|
-
this.taskCompleted = output();
|
|
15446
|
-
// Dependencies
|
|
15447
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
15448
|
-
this.datePipe = inject(DatePipe);
|
|
15449
|
-
// Configuration options
|
|
15450
|
-
this.maxItems = computed(() => this.options()?.maxItems ?? 10);
|
|
15451
|
-
this.showDate = computed(() => this.options()?.showDate ?? true);
|
|
15452
|
-
this.showAssignee = computed(() => this.options()?.showAssignee ?? true);
|
|
15453
|
-
this.showPriority = computed(() => this.options()?.showPriority ?? true);
|
|
15454
|
-
this.allowMarkComplete = computed(() => this.options()?.allowMarkComplete ?? true);
|
|
15455
|
-
this.showCategories = computed(() => this.options()?.groupByCategory ?? true);
|
|
15456
|
-
// Data computed properties
|
|
15457
|
-
this.taskItems = computed(() => {
|
|
15458
|
-
const value = this.getValue();
|
|
15459
|
-
if (!value?.data?.length)
|
|
15460
|
-
return [];
|
|
15461
|
-
return value.data.slice(0, this.maxItems());
|
|
15462
|
-
});
|
|
15463
|
-
}
|
|
15464
|
-
// Task counting methods
|
|
15465
|
-
getPendingTaskCount() {
|
|
15466
|
-
return this.getFilteredTasks((task) => !task.completed).length;
|
|
15467
|
-
}
|
|
15468
|
-
getCompletedTaskCount() {
|
|
15469
|
-
return this.getFilteredTasks((task) => task.completed).length;
|
|
15470
|
-
}
|
|
15471
|
-
// Category-related methods
|
|
15472
|
-
hasCategories() {
|
|
15473
|
-
return this.taskItems().some((task) => !!task.category);
|
|
15474
|
-
}
|
|
15475
|
-
getCategories() {
|
|
15476
|
-
const tasks = this.taskItems();
|
|
15477
|
-
const categories = new Set();
|
|
15478
|
-
let hasUncategorized = false;
|
|
15479
|
-
tasks.forEach((task) => {
|
|
15480
|
-
if (task.category) {
|
|
15481
|
-
categories.add(task.category);
|
|
15482
|
-
}
|
|
15483
|
-
else {
|
|
15484
|
-
hasUncategorized = true;
|
|
15485
|
-
}
|
|
15486
|
-
});
|
|
15487
|
-
const result = Array.from(categories);
|
|
15488
|
-
if (hasUncategorized) {
|
|
15489
|
-
result.push('Uncategorized');
|
|
15490
|
-
}
|
|
15491
|
-
return result;
|
|
15492
|
-
}
|
|
15493
|
-
getTasksByCategory(category) {
|
|
15494
|
-
const tasks = this.taskItems();
|
|
15495
|
-
if (category === 'Uncategorized') {
|
|
15496
|
-
return tasks.filter((task) => !task.category);
|
|
15497
|
-
}
|
|
15498
|
-
return tasks.filter((task) => task.category === category);
|
|
15499
|
-
}
|
|
15500
|
-
getCategoryTaskCount(category) {
|
|
15501
|
-
return this.getTasksByCategory(category).filter((task) => !task.completed).length;
|
|
15502
|
-
}
|
|
15503
|
-
// Event handlers
|
|
15504
|
-
onTaskClick(task) {
|
|
15505
|
-
this.taskClick.emit(task);
|
|
15506
|
-
}
|
|
15507
|
-
onTaskCompletionChange(task, isCompleted) {
|
|
15508
|
-
const updatedTask = { ...task, completed: isCompleted };
|
|
15509
|
-
const value = this.getValue();
|
|
15510
|
-
if (!value?.data)
|
|
15511
|
-
return;
|
|
15512
|
-
const updatedTasks = value.data.map((t) => (t.id === task.id ? updatedTask : t));
|
|
15513
|
-
this.setValue({ ...value, data: updatedTasks });
|
|
15514
|
-
this.taskCompleted.emit(updatedTask);
|
|
15515
|
-
this.cdr.detectChanges();
|
|
15516
|
-
}
|
|
15517
|
-
// Formatting and utility methods
|
|
15518
|
-
formatDueDate(date) {
|
|
15519
|
-
if (!date)
|
|
15520
|
-
return '';
|
|
15521
|
-
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
|
15522
|
-
const diffDays = this.getDaysDifference(dateObj);
|
|
15523
|
-
if (diffDays < 0)
|
|
15524
|
-
return 'Overdue!';
|
|
15525
|
-
if (diffDays === 0)
|
|
15526
|
-
return 'Today';
|
|
15527
|
-
if (diffDays === 1)
|
|
15528
|
-
return 'Tomorrow';
|
|
15529
|
-
if (diffDays < 7)
|
|
15530
|
-
return this.datePipe.transform(dateObj, 'EEE') || '';
|
|
15531
|
-
return this.datePipe.transform(dateObj, 'MM/dd/yyyy') || '';
|
|
15532
|
-
}
|
|
15533
|
-
getPriorityColor(priority) {
|
|
15534
|
-
if (!priority)
|
|
15535
|
-
return '';
|
|
15536
|
-
const priorityColors = {
|
|
15537
|
-
high: 'danger',
|
|
15538
|
-
medium: 'warning',
|
|
15539
|
-
low: 'success',
|
|
15540
|
-
};
|
|
15541
|
-
return priorityColors[priority] || '';
|
|
15542
|
-
}
|
|
15543
|
-
// Helper methods
|
|
15544
|
-
getFilteredTasks(filterFn) {
|
|
15545
|
-
const value = this.getValue();
|
|
15546
|
-
if (!value?.data?.length)
|
|
15547
|
-
return [];
|
|
15548
|
-
return value.data.filter(filterFn);
|
|
15549
|
-
}
|
|
15550
|
-
getDaysDifference(date) {
|
|
15551
|
-
const now = new Date();
|
|
15552
|
-
now.setHours(0, 0, 0, 0);
|
|
15553
|
-
const targetDate = new Date(date);
|
|
15554
|
-
targetDate.setHours(0, 0, 0, 0);
|
|
15555
|
-
const diffMs = targetDate.getTime() - now.getTime();
|
|
15556
|
-
return Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
15557
|
-
}
|
|
15558
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPTaskListWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
15559
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: AXPTaskListWidgetViewComponent, isStandalone: true, selector: "ng-component", outputs: { taskClick: "taskClick", taskCompleted: "taskCompleted" }, providers: [DatePipe], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-size-full ax-p-4\">\n <!-- Header -->\n <div class=\"ax-flex ax-justify-between ax-items-center ax-mb-3\">\n <h3 class=\"ax-text-lg ax-font-semibold\">{{ 'widget.tasklist.title' | translate | async }}</h3>\n <div class=\"ax-flex ax-gap-2\">\n @if(getPendingTaskCount() > 0){\n <ax-badge\n [text]=\"getPendingTaskCount() + ' ' + ('widget.tasklist.pending' | translate | async)\"\n [color]=\"'warning'\"\n size=\"sm\"\n class=\"ax-ml-1\"\n >\n </ax-badge>\n } @if(getCompletedTaskCount() > 0){\n <ax-badge\n [text]=\"getCompletedTaskCount() + ' ' + ('widget.tasklist.completed' | translate | async)\"\n [color]=\"'success'\"\n size=\"sm\"\n class=\"ax-ml-1\"\n >\n </ax-badge>\n }\n </div>\n </div>\n\n <!-- Task List -->\n <div class=\"ax-space-y-4 ax-my-4 ax-px-2\">\n @if (showCategories() && hasCategories()) {\n <!-- Categorized Tasks -->\n @for (category of getCategories(); track category) {\n <div class=\"ax-mb-3\">\n <!-- Category Header -->\n <div class=\"ax-flex ax-justify-between ax-items-center ax-mb-2 ax-h-5\">\n <h4 class=\"ax-font-medium ax-text-gray-700\">{{ category }}</h4>\n @if(getCategoryTaskCount(category)){\n <ax-badge [text]=\"getCategoryTaskCount(category).toString()\" [color]=\"'primary'\" size=\"sm\"></ax-badge>\n }\n </div>\n <!-- Tasks in Category -->\n @for (task of getTasksByCategory(category); track task.id) {\n <ng-container\n [ngTemplateOutlet]=\"taskItemTemplateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: task }\"\n ></ng-container>\n }\n </div>\n } } @else {\n <!-- Uncategorized Tasks -->\n @for (task of taskItems(); track task.id) {\n <ng-container\n [ngTemplateOutlet]=\"taskItemTemplateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: task }\"\n ></ng-container>\n } @empty {\n <!-- Empty State -->\n <div class=\"ax-flex ax-flex-col ax-items-center ax-justify-center ax-py-12 ax-px-4 ax-text-gray-400\">\n <ax-icon class=\"ax-text-4xl ax-mb-3 ax-text-gray-300\">\n <i class=\"fa-light fa-clipboard-list-check\"></i>\n </ax-icon>\n <p class=\"ax-text-center\">{{ 'widget.tasklist.noTasks' | translate | async }}</p>\n </div>\n } }\n </div>\n</div>\n\n<!-- Task Item Template -->\n<ng-template #taskItemTemplateRef let-task>\n <div class=\"ax-flex ax-gap-3 ax-items-center ax-py-2 ax-border-b ax-border-gray-100 last:ax-border-0\">\n <!-- Checkbox -->\n <ax-check-box\n class=\"ax-flex-shrink-0\"\n [value]=\"task.completed\"\n [disabled]=\"!allowMarkComplete()\"\n (valueChange)=\"onTaskCompletionChange(task, $event)\"\n >\n </ax-check-box>\n\n <!-- Task Details -->\n <div class=\"ax-overflow-hidden ax-grow ax-text-start\" (click)=\"onTaskClick(task)\">\n <!-- Title and Priority -->\n <div class=\"ax-flex ax-items-center ax-gap-2\">\n <h6\n class=\"ax-font-semibold ax-truncate ax-pb-1\"\n [class.ax-line-through]=\"task.completed\"\n [class.ax-text-gray-400]=\"task.completed\"\n >\n {{ task.title }}\n </h6>\n @if(showPriority() && task.priority) {\n <ax-badge [color]=\"getPriorityColor(task.priority)\" [text]=\"task.priority\" size=\"sm\" class=\"ax-ml-1\"></ax-badge>\n }\n </div>\n\n <!-- Metadata -->\n <div class=\"ax-flex ax-flex-wrap ax-gap-x-3 ax-gap-y-1 ax-mt-1 ax-text-xs ax-text-gray-500\">\n @if(showDate() && task.dueDate) {\n <span class=\"ax-flex ax-items-center ax-gap-1\" [class.ax-text-danger-500]=\"getDaysDifference(task.dueDate) < 0\">\n <ax-icon><i class=\"fa-light fa-calendar\"></i></ax-icon>\n {{ formatDueDate(task.dueDate) }}\n </span>\n } @if(showAssignee() && task.assignedTo) {\n <span class=\"ax-flex ax-items-center ax-gap-1\">\n <ax-icon><i class=\"fa-light fa-user\"></i></ax-icon>\n {{ task.assignedTo.name }}\n </span>\n }\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;height:100%;width:100%}.task-completed{text-decoration:line-through;color:var(--ax-text-secondary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: AXTabsModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i1$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXAvatarModule }, { kind: "ngmodule", type: AXImageModule }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "component", type: i1.AXCheckBoxComponent, selector: "ax-check-box", inputs: ["disabled", "tabIndex", "readonly", "color", "value", "name", "id", "checked", "indeterminate"], outputs: ["onBlur", "onFocus", "valueChange", "onValueChanged"] }, { kind: "ngmodule", type: AXLabelModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i5$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
15560
|
-
}
|
|
15561
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPTaskListWidgetViewComponent, decorators: [{
|
|
15562
|
-
type: Component,
|
|
15563
|
-
args: [{ standalone: true, imports: [
|
|
15564
|
-
CommonModule,
|
|
15565
|
-
AXTabsModule,
|
|
15566
|
-
AXDecoratorModule,
|
|
15567
|
-
AXButtonModule,
|
|
15568
|
-
AXBadgeModule,
|
|
15569
|
-
AXAvatarModule,
|
|
15570
|
-
AXImageModule,
|
|
15571
|
-
AXCheckBoxModule,
|
|
15572
|
-
AXLabelModule,
|
|
15573
|
-
AXTranslationModule,
|
|
15574
|
-
], providers: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-size-full ax-p-4\">\n <!-- Header -->\n <div class=\"ax-flex ax-justify-between ax-items-center ax-mb-3\">\n <h3 class=\"ax-text-lg ax-font-semibold\">{{ 'widget.tasklist.title' | translate | async }}</h3>\n <div class=\"ax-flex ax-gap-2\">\n @if(getPendingTaskCount() > 0){\n <ax-badge\n [text]=\"getPendingTaskCount() + ' ' + ('widget.tasklist.pending' | translate | async)\"\n [color]=\"'warning'\"\n size=\"sm\"\n class=\"ax-ml-1\"\n >\n </ax-badge>\n } @if(getCompletedTaskCount() > 0){\n <ax-badge\n [text]=\"getCompletedTaskCount() + ' ' + ('widget.tasklist.completed' | translate | async)\"\n [color]=\"'success'\"\n size=\"sm\"\n class=\"ax-ml-1\"\n >\n </ax-badge>\n }\n </div>\n </div>\n\n <!-- Task List -->\n <div class=\"ax-space-y-4 ax-my-4 ax-px-2\">\n @if (showCategories() && hasCategories()) {\n <!-- Categorized Tasks -->\n @for (category of getCategories(); track category) {\n <div class=\"ax-mb-3\">\n <!-- Category Header -->\n <div class=\"ax-flex ax-justify-between ax-items-center ax-mb-2 ax-h-5\">\n <h4 class=\"ax-font-medium ax-text-gray-700\">{{ category }}</h4>\n @if(getCategoryTaskCount(category)){\n <ax-badge [text]=\"getCategoryTaskCount(category).toString()\" [color]=\"'primary'\" size=\"sm\"></ax-badge>\n }\n </div>\n <!-- Tasks in Category -->\n @for (task of getTasksByCategory(category); track task.id) {\n <ng-container\n [ngTemplateOutlet]=\"taskItemTemplateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: task }\"\n ></ng-container>\n }\n </div>\n } } @else {\n <!-- Uncategorized Tasks -->\n @for (task of taskItems(); track task.id) {\n <ng-container\n [ngTemplateOutlet]=\"taskItemTemplateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: task }\"\n ></ng-container>\n } @empty {\n <!-- Empty State -->\n <div class=\"ax-flex ax-flex-col ax-items-center ax-justify-center ax-py-12 ax-px-4 ax-text-gray-400\">\n <ax-icon class=\"ax-text-4xl ax-mb-3 ax-text-gray-300\">\n <i class=\"fa-light fa-clipboard-list-check\"></i>\n </ax-icon>\n <p class=\"ax-text-center\">{{ 'widget.tasklist.noTasks' | translate | async }}</p>\n </div>\n } }\n </div>\n</div>\n\n<!-- Task Item Template -->\n<ng-template #taskItemTemplateRef let-task>\n <div class=\"ax-flex ax-gap-3 ax-items-center ax-py-2 ax-border-b ax-border-gray-100 last:ax-border-0\">\n <!-- Checkbox -->\n <ax-check-box\n class=\"ax-flex-shrink-0\"\n [value]=\"task.completed\"\n [disabled]=\"!allowMarkComplete()\"\n (valueChange)=\"onTaskCompletionChange(task, $event)\"\n >\n </ax-check-box>\n\n <!-- Task Details -->\n <div class=\"ax-overflow-hidden ax-grow ax-text-start\" (click)=\"onTaskClick(task)\">\n <!-- Title and Priority -->\n <div class=\"ax-flex ax-items-center ax-gap-2\">\n <h6\n class=\"ax-font-semibold ax-truncate ax-pb-1\"\n [class.ax-line-through]=\"task.completed\"\n [class.ax-text-gray-400]=\"task.completed\"\n >\n {{ task.title }}\n </h6>\n @if(showPriority() && task.priority) {\n <ax-badge [color]=\"getPriorityColor(task.priority)\" [text]=\"task.priority\" size=\"sm\" class=\"ax-ml-1\"></ax-badge>\n }\n </div>\n\n <!-- Metadata -->\n <div class=\"ax-flex ax-flex-wrap ax-gap-x-3 ax-gap-y-1 ax-mt-1 ax-text-xs ax-text-gray-500\">\n @if(showDate() && task.dueDate) {\n <span class=\"ax-flex ax-items-center ax-gap-1\" [class.ax-text-danger-500]=\"getDaysDifference(task.dueDate) < 0\">\n <ax-icon><i class=\"fa-light fa-calendar\"></i></ax-icon>\n {{ formatDueDate(task.dueDate) }}\n </span>\n } @if(showAssignee() && task.assignedTo) {\n <span class=\"ax-flex ax-items-center ax-gap-1\">\n <ax-icon><i class=\"fa-light fa-user\"></i></ax-icon>\n {{ task.assignedTo.name }}\n </span>\n }\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;height:100%;width:100%}.task-completed{text-decoration:line-through;color:var(--ax-text-secondary)}\n"] }]
|
|
15575
|
-
}] });
|
|
15576
|
-
|
|
15577
|
-
var tasklistWidget_component = /*#__PURE__*/Object.freeze({
|
|
15578
|
-
__proto__: null,
|
|
15579
|
-
AXPTaskListWidgetViewComponent: AXPTaskListWidgetViewComponent
|
|
15580
|
-
});
|
|
15581
|
-
|
|
15582
|
-
const AXPTaskListWidget = {
|
|
15583
|
-
name: 'task-list',
|
|
15584
|
-
title: 'Task List Widget',
|
|
15585
|
-
categories: [AXP_WIDGETS_CHART_CATEGORY],
|
|
15586
|
-
groups: [AXPWidgetGroupEnum.DashboardWidget],
|
|
15587
|
-
type: 'dashboard',
|
|
15588
|
-
icon: 'fa-light fa-clipboard-list-check',
|
|
15589
|
-
properties: [
|
|
15590
|
-
cloneProperty(AXP_DATA_PATH_PROPERTY, { visible: false }),
|
|
15591
|
-
// Display options
|
|
15592
|
-
{
|
|
15593
|
-
name: 'maxItems',
|
|
15594
|
-
title: 'Max Items',
|
|
15595
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15596
|
-
schema: {
|
|
15597
|
-
defaultValue: 10,
|
|
15598
|
-
dataType: 'number',
|
|
15599
|
-
interface: {
|
|
15600
|
-
name: 'maxItems',
|
|
15601
|
-
path: 'options.maxItems',
|
|
15602
|
-
type: AXPWidgetsCatalog.number,
|
|
15603
|
-
options: {
|
|
15604
|
-
minValue: 1,
|
|
15605
|
-
maxValue: 50,
|
|
15606
|
-
},
|
|
15607
|
-
},
|
|
15608
|
-
},
|
|
15609
|
-
visible: true,
|
|
15610
|
-
},
|
|
15611
|
-
{
|
|
15612
|
-
name: 'showDate',
|
|
15613
|
-
title: 'Show Date',
|
|
15614
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15615
|
-
schema: {
|
|
15616
|
-
defaultValue: true,
|
|
15617
|
-
dataType: 'boolean',
|
|
15618
|
-
interface: {
|
|
15619
|
-
name: 'showDate',
|
|
15620
|
-
path: 'options.showDate',
|
|
15621
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15622
|
-
},
|
|
15623
|
-
},
|
|
15624
|
-
visible: true,
|
|
15625
|
-
},
|
|
15626
|
-
{
|
|
15627
|
-
name: 'showAssignee',
|
|
15628
|
-
title: 'Show Assignee',
|
|
15629
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15630
|
-
schema: {
|
|
15631
|
-
defaultValue: true,
|
|
15632
|
-
dataType: 'boolean',
|
|
15633
|
-
interface: {
|
|
15634
|
-
name: 'showAssignee',
|
|
15635
|
-
path: 'options.showAssignee',
|
|
15636
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15637
|
-
},
|
|
15638
|
-
},
|
|
15639
|
-
visible: true,
|
|
15640
|
-
},
|
|
15641
|
-
{
|
|
15642
|
-
name: 'groupByCategory',
|
|
15643
|
-
title: 'Group by Category',
|
|
15644
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15645
|
-
schema: {
|
|
15646
|
-
defaultValue: true,
|
|
15647
|
-
dataType: 'boolean',
|
|
15648
|
-
interface: {
|
|
15649
|
-
name: 'groupByCategory',
|
|
15650
|
-
path: 'options.groupByCategory',
|
|
15651
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15652
|
-
},
|
|
15653
|
-
},
|
|
15654
|
-
visible: true,
|
|
15655
|
-
},
|
|
15656
|
-
{
|
|
15657
|
-
name: 'showPriority',
|
|
15658
|
-
title: 'Show Priority',
|
|
15659
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15660
|
-
schema: {
|
|
15661
|
-
defaultValue: true,
|
|
15662
|
-
dataType: 'boolean',
|
|
15663
|
-
interface: {
|
|
15664
|
-
name: 'showPriority',
|
|
15665
|
-
path: 'options.showPriority',
|
|
15666
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15667
|
-
},
|
|
15668
|
-
},
|
|
15669
|
-
visible: true,
|
|
15670
|
-
},
|
|
15671
|
-
{
|
|
15672
|
-
name: 'allowMarkComplete',
|
|
15673
|
-
title: 'Allow Complete',
|
|
15674
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
15675
|
-
schema: {
|
|
15676
|
-
defaultValue: true,
|
|
15677
|
-
dataType: 'boolean',
|
|
15678
|
-
interface: {
|
|
15679
|
-
name: 'allowMarkComplete',
|
|
15680
|
-
path: 'options.allowMarkComplete',
|
|
15681
|
-
type: AXPWidgetsCatalog.toggle,
|
|
15682
|
-
},
|
|
15683
|
-
},
|
|
15684
|
-
visible: true,
|
|
15685
|
-
},
|
|
15686
|
-
],
|
|
15687
|
-
components: {
|
|
15688
|
-
view: {
|
|
15689
|
-
component: () => Promise.resolve().then(function () { return tasklistWidget_component; }).then((c) => c.AXPTaskListWidgetViewComponent),
|
|
15690
|
-
},
|
|
15691
|
-
},
|
|
15692
|
-
meta: {
|
|
15693
|
-
dimensions: {
|
|
15694
|
-
width: 5,
|
|
15695
|
-
height: 7,
|
|
15696
|
-
minWidth: 3,
|
|
15697
|
-
minHeight: 4,
|
|
15698
|
-
maxWidth: 6,
|
|
15699
|
-
maxHeight: 8,
|
|
15700
|
-
},
|
|
15701
|
-
},
|
|
15702
|
-
};
|
|
15703
|
-
|
|
15704
|
-
/**
|
|
15705
|
-
* Abstract Weather API Service
|
|
15706
|
-
* Base class that defines the interface and common functionality
|
|
15707
|
-
* for weather data providers
|
|
15708
|
-
*/
|
|
15709
|
-
class AXPWeatherApiAbstract {
|
|
15710
|
-
constructor() {
|
|
15711
|
-
/** Weather condition definitions mapping */
|
|
15712
|
-
this.weatherConditions = {
|
|
15713
|
-
sunny: {
|
|
15714
|
-
id: 'sunny',
|
|
15715
|
-
name: 'Sunny',
|
|
15716
|
-
icon: 'fa-solid fa-sun',
|
|
15717
|
-
color: '#ff9d00',
|
|
15718
|
-
},
|
|
15719
|
-
clearNight: {
|
|
15720
|
-
id: 'clearNight',
|
|
15721
|
-
name: 'Clear Night',
|
|
15722
|
-
icon: 'fa-solid fa-moon',
|
|
15723
|
-
color: '#5d639e',
|
|
15724
|
-
},
|
|
15725
|
-
partlyCloudy: {
|
|
15726
|
-
id: 'partlyCloudy',
|
|
15727
|
-
name: 'Partly Cloudy',
|
|
15728
|
-
icon: 'fa-solid fa-cloud-sun',
|
|
15729
|
-
color: '#6ba4e8',
|
|
15730
|
-
},
|
|
15731
|
-
partlyCloudyNight: {
|
|
15732
|
-
id: 'partlyCloudyNight',
|
|
15733
|
-
name: 'Partly Cloudy Night',
|
|
15734
|
-
icon: 'fa-solid fa-cloud-moon',
|
|
15735
|
-
color: '#5d639e',
|
|
15736
|
-
},
|
|
15737
|
-
cloudy: {
|
|
15738
|
-
id: 'cloudy',
|
|
15739
|
-
name: 'Cloudy',
|
|
15740
|
-
icon: 'fa-solid fa-cloud',
|
|
15741
|
-
color: '#72869d',
|
|
15742
|
-
},
|
|
15743
|
-
rain: {
|
|
15744
|
-
id: 'rain',
|
|
15745
|
-
name: 'Rain',
|
|
15746
|
-
icon: 'fa-solid fa-cloud-rain',
|
|
15747
|
-
color: '#3a74ad',
|
|
15748
|
-
},
|
|
15749
|
-
showers: {
|
|
15750
|
-
id: 'showers',
|
|
15751
|
-
name: 'Showers',
|
|
15752
|
-
icon: 'fa-solid fa-cloud-showers-heavy',
|
|
15753
|
-
color: '#2c5d8c',
|
|
15754
|
-
},
|
|
15755
|
-
thunderstorm: {
|
|
15756
|
-
id: 'thunderstorm',
|
|
15757
|
-
name: 'Thunderstorm',
|
|
15758
|
-
icon: 'fa-solid fa-bolt-lightning',
|
|
15759
|
-
color: '#8834af',
|
|
15760
|
-
},
|
|
15761
|
-
snow: {
|
|
15762
|
-
id: 'snow',
|
|
15763
|
-
name: 'Snow',
|
|
15764
|
-
icon: 'fa-solid fa-snowflake',
|
|
15765
|
-
color: '#68a9cd',
|
|
15766
|
-
},
|
|
15767
|
-
mist: {
|
|
15768
|
-
id: 'mist',
|
|
15769
|
-
name: 'Mist',
|
|
15770
|
-
icon: 'fa-solid fa-smog',
|
|
15771
|
-
color: '#94a3b8',
|
|
15772
|
-
},
|
|
15773
|
-
};
|
|
15774
|
-
/** Day of week mapping */
|
|
15775
|
-
this.dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
15776
|
-
}
|
|
15777
|
-
/**
|
|
15778
|
-
* Get weather condition info by ID
|
|
15779
|
-
* @param id Condition ID
|
|
15780
|
-
* @returns Weather condition info or default if not found
|
|
15781
|
-
*/
|
|
15782
|
-
getCondition(id) {
|
|
15783
|
-
// Normalize condition ID by removing spaces and converting to lowercase
|
|
15784
|
-
const normalizedId = id.toLowerCase().replace(/\s+/g, '');
|
|
15785
|
-
// Direct match first
|
|
15786
|
-
if (this.weatherConditions[normalizedId]) {
|
|
15787
|
-
return this.weatherConditions[normalizedId];
|
|
15788
|
-
}
|
|
15789
|
-
// Check for partial matches or common variants
|
|
15790
|
-
if (normalizedId.includes('partly') && normalizedId.includes('cloud')) {
|
|
15791
|
-
return this.weatherConditions['partlyCloudy'];
|
|
15792
|
-
}
|
|
15793
|
-
if (normalizedId.includes('cloud')) {
|
|
15794
|
-
return this.weatherConditions['cloudy'];
|
|
15795
|
-
}
|
|
15796
|
-
if (normalizedId.includes('sun')) {
|
|
15797
|
-
return this.weatherConditions['sunny'];
|
|
15798
|
-
}
|
|
15799
|
-
if (normalizedId.includes('rain')) {
|
|
15800
|
-
return this.weatherConditions['rain'];
|
|
15801
|
-
}
|
|
15802
|
-
if (normalizedId.includes('snow')) {
|
|
15803
|
-
return this.weatherConditions['snow'];
|
|
15804
|
-
}
|
|
15805
|
-
// If no match found, return default unknown condition
|
|
15806
|
-
return {
|
|
15807
|
-
id: 'unknown',
|
|
15808
|
-
name: 'Unknown',
|
|
15809
|
-
icon: 'fa-solid fa-question',
|
|
15810
|
-
color: '#999999',
|
|
15811
|
-
};
|
|
15812
|
-
}
|
|
15813
|
-
/**
|
|
15814
|
-
* Parse location string into city and display components
|
|
15815
|
-
* @param location Location query string
|
|
15816
|
-
* @returns Parsed location parts
|
|
15817
|
-
*/
|
|
15818
|
-
parseLocation(location) {
|
|
15819
|
-
if (!location || location.trim() === '') {
|
|
15820
|
-
return { city: 'New York', country: 'USA', display: 'New York' };
|
|
15821
|
-
}
|
|
15822
|
-
const city = location.trim();
|
|
15823
|
-
return { city, country: '', display: city };
|
|
15824
|
-
}
|
|
15825
|
-
/**
|
|
15826
|
-
* Format location name for display
|
|
15827
|
-
* @param locationParts Parsed location parts
|
|
15828
|
-
* @returns Formatted location name
|
|
15829
|
-
*/
|
|
15830
|
-
formatLocationName(locationParts) {
|
|
15831
|
-
return locationParts.city || 'Unknown Location';
|
|
15832
|
-
}
|
|
15833
|
-
/**
|
|
15834
|
-
* Map API condition text to our internal condition IDs
|
|
15835
|
-
* @param conditionText Condition text from API
|
|
15836
|
-
* @returns Internal condition ID
|
|
15837
|
-
*/
|
|
15838
|
-
mapApiConditionToId(conditionText) {
|
|
15839
|
-
const text = conditionText.toLowerCase();
|
|
15840
|
-
// Match WeatherAPI.com condition text
|
|
15841
|
-
// Reference: https://www.weatherapi.com/docs/#weather-icons
|
|
15842
|
-
if (text.includes('sunny') || text.includes('clear')) {
|
|
15843
|
-
return 'sunny';
|
|
15844
|
-
}
|
|
15845
|
-
if (text === 'partly cloudy') {
|
|
15846
|
-
return 'partlyCloudy';
|
|
15847
|
-
}
|
|
15848
|
-
if (text.includes('cloudy') || text.includes('overcast')) {
|
|
15849
|
-
return 'cloudy';
|
|
15850
|
-
}
|
|
15851
|
-
if (text.includes('rain') || text.includes('drizzle') || text.includes('shower')) {
|
|
15852
|
-
return 'rain';
|
|
15853
|
-
}
|
|
15854
|
-
if (text.includes('thunder') || text.includes('lightning')) {
|
|
15855
|
-
return 'thunderstorm';
|
|
15856
|
-
}
|
|
15857
|
-
if (text.includes('snow') || text.includes('sleet') || text.includes('blizzard')) {
|
|
15858
|
-
return 'snow';
|
|
15859
|
-
}
|
|
15860
|
-
if (text.includes('mist') || text.includes('fog')) {
|
|
15861
|
-
return 'mist';
|
|
15862
|
-
}
|
|
15863
|
-
// Default to partly cloudy if we don't have a specific mapping
|
|
15864
|
-
return 'partlyCloudy';
|
|
15865
|
-
}
|
|
15866
|
-
/**
|
|
15867
|
-
* Generate random integer between min and max (inclusive)
|
|
15868
|
-
* Helper method for implementations
|
|
15869
|
-
* @param min Minimum value
|
|
15870
|
-
* @param max Maximum value
|
|
15871
|
-
* @returns Random integer
|
|
15872
|
-
*/
|
|
15873
|
-
getRandomInt(min, max) {
|
|
15874
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
15875
|
-
}
|
|
15876
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiAbstract, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
15877
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiAbstract }); }
|
|
15878
|
-
}
|
|
15879
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiAbstract, decorators: [{
|
|
15880
|
-
type: Injectable
|
|
15881
|
-
}] });
|
|
15882
|
-
|
|
15883
|
-
/**
|
|
15884
|
-
* Mock Weather API Service
|
|
15885
|
-
* Provides simulated weather data for development and testing
|
|
15886
|
-
*/
|
|
15887
|
-
class AXPWeatherApiMockService extends AXPWeatherApiAbstract {
|
|
15888
|
-
constructor() {
|
|
15889
|
-
super(...arguments);
|
|
15890
|
-
// Mock configuration
|
|
15891
|
-
this.mockDelay = 500;
|
|
15892
|
-
}
|
|
15893
|
-
/**
|
|
15894
|
-
* Get current weather data for given location
|
|
15895
|
-
* @param options Weather request options
|
|
15896
|
-
* @returns Observable with weather data
|
|
15897
|
-
*/
|
|
15898
|
-
getWeather(options) {
|
|
15899
|
-
const locationParts = this.parseLocation(options.location || '');
|
|
15900
|
-
const displayName = this.formatLocationName(locationParts);
|
|
15901
|
-
return this.getMockWeatherData(locationParts.city, options).pipe(map((mockData) => ({
|
|
15902
|
-
...mockData,
|
|
15903
|
-
location: {
|
|
15904
|
-
city: locationParts.city,
|
|
15905
|
-
country: '',
|
|
15906
|
-
displayName: displayName,
|
|
15907
|
-
},
|
|
15908
|
-
})));
|
|
15909
|
-
}
|
|
15910
|
-
/**
|
|
15911
|
-
* Get weather forecast for a location
|
|
15912
|
-
* @param options Weather request options with days
|
|
15913
|
-
* @returns Observable of weather data with forecast
|
|
15914
|
-
*/
|
|
15915
|
-
getForecast(options) {
|
|
15916
|
-
const days = options.days || 5;
|
|
15917
|
-
return this.getMockForecast(options.location || '', days, options);
|
|
15918
|
-
}
|
|
15919
|
-
/**
|
|
15920
|
-
* Get mock weather data for demo/development
|
|
15921
|
-
* @param location Location query
|
|
15922
|
-
* @param options Request options
|
|
15923
|
-
* @returns Observable of mock weather data
|
|
15924
|
-
*/
|
|
15925
|
-
getMockWeatherData(location, options) {
|
|
15926
|
-
return new Observable((observer) => {
|
|
15927
|
-
setTimeout(() => {
|
|
15928
|
-
try {
|
|
15929
|
-
const locationParts = this.parseLocation(location);
|
|
15930
|
-
const tempUnit = options?.tempUnit || '°C';
|
|
15931
|
-
const isCelsius = tempUnit === '°C';
|
|
15932
|
-
// Generate more realistic weather data based on location and current date
|
|
15933
|
-
const now = new Date();
|
|
15934
|
-
const month = now.getMonth(); // 0-11
|
|
15935
|
-
// Seasonally appropriate temperature range
|
|
15936
|
-
let minRange = 15;
|
|
15937
|
-
let maxRange = 25;
|
|
15938
|
-
// Northern hemisphere seasonal adjustments
|
|
15939
|
-
if (month >= 11 || month <= 1) {
|
|
15940
|
-
// Winter
|
|
15941
|
-
minRange = -5;
|
|
15942
|
-
maxRange = 10;
|
|
15943
|
-
}
|
|
15944
|
-
else if (month >= 2 && month <= 4) {
|
|
15945
|
-
// Spring
|
|
15946
|
-
minRange = 10;
|
|
15947
|
-
maxRange = 20;
|
|
15948
|
-
}
|
|
15949
|
-
else if (month >= 5 && month <= 7) {
|
|
15950
|
-
// Summer
|
|
15951
|
-
minRange = 20;
|
|
15952
|
-
maxRange = 35;
|
|
15953
|
-
}
|
|
15954
|
-
else if (month >= 8 && month <= 10) {
|
|
15955
|
-
// Fall/Autumn
|
|
15956
|
-
minRange = 10;
|
|
15957
|
-
maxRange = 25;
|
|
15958
|
-
}
|
|
15959
|
-
// Generate current temperature - more likely to be in the middle of the range
|
|
15960
|
-
const tempC = Math.round(minRange + (this.getRandomInt(4, 8) / 10) * (maxRange - minRange));
|
|
15961
|
-
const tempF = Math.round((tempC * 9) / 5 + 32);
|
|
15962
|
-
const humidity = this.getRandomInt(30, 90);
|
|
15963
|
-
const windKph = this.getRandomInt(5, 30);
|
|
15964
|
-
const windMph = Math.round(windKph * 0.621371);
|
|
15965
|
-
// Temperature-based conditions with randomization
|
|
15966
|
-
let conditionId = 'partlyCloudy';
|
|
15967
|
-
if (tempC > 25)
|
|
15968
|
-
conditionId = 'sunny';
|
|
15969
|
-
else if (tempC < 10)
|
|
15970
|
-
conditionId = 'cloudy';
|
|
15971
|
-
if (this.getRandomInt(1, 10) > 7)
|
|
15972
|
-
conditionId = 'rain';
|
|
15973
|
-
observer.next({
|
|
15974
|
-
location: {
|
|
15975
|
-
city: locationParts.city,
|
|
15976
|
-
country: '',
|
|
15977
|
-
displayName: this.formatLocationName(locationParts),
|
|
15978
|
-
},
|
|
15979
|
-
current: {
|
|
15980
|
-
condition: conditionId,
|
|
15981
|
-
conditionCode: 0,
|
|
15982
|
-
iconUrl: '', // Would be set from API
|
|
15983
|
-
tempC: tempC,
|
|
15984
|
-
tempF: tempF,
|
|
15985
|
-
feelsLikeC: tempC + this.getRandomInt(-3, 2),
|
|
15986
|
-
feelsLikeF: tempF + this.getRandomInt(-5, 3),
|
|
15987
|
-
humidity: humidity,
|
|
15988
|
-
windKph: windKph,
|
|
15989
|
-
windMph: windMph,
|
|
15990
|
-
windDirection: ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'][this.getRandomInt(0, 7)],
|
|
15991
|
-
uv: this.getRandomInt(0, 10),
|
|
15992
|
-
lastUpdated: new Date().toISOString(),
|
|
15993
|
-
},
|
|
15994
|
-
forecast: [],
|
|
15995
|
-
});
|
|
15996
|
-
observer.complete();
|
|
15997
|
-
}
|
|
15998
|
-
catch (error) {
|
|
15999
|
-
observer.error(error);
|
|
16000
|
-
}
|
|
16001
|
-
}, this.mockDelay);
|
|
16002
|
-
}).pipe(catchError((error) => throwError(() => new Error(`Failed to get weather data: ${error.message}`))));
|
|
16003
|
-
}
|
|
16004
|
-
/**
|
|
16005
|
-
* Get mock forecast data for demo/development
|
|
16006
|
-
* @param location Location query
|
|
16007
|
-
* @param days Number of forecast days
|
|
16008
|
-
* @param options Request options
|
|
16009
|
-
* @returns Observable of mock weather data with forecast
|
|
16010
|
-
*/
|
|
16011
|
-
getMockForecast(location, days = 5, options) {
|
|
16012
|
-
return this.getMockWeatherData(location, options).pipe(map((data) => {
|
|
16013
|
-
const currentCondition = data.current.condition;
|
|
16014
|
-
const currentTemp = data.current.tempC;
|
|
16015
|
-
// Use the current weather as a base for the forecast trend
|
|
16016
|
-
const forecast = Array.from({ length: days }, (_, i) => {
|
|
16017
|
-
// Create forecast date for each day (starting from tomorrow)
|
|
16018
|
-
const forecastDate = new Date();
|
|
16019
|
-
forecastDate.setDate(new Date().getDate() + i + 1);
|
|
16020
|
-
const dateStr = forecastDate.toISOString().split('T')[0];
|
|
16021
|
-
const dayName = this.dayNames[forecastDate.getDay()];
|
|
16022
|
-
// Generate condition with some continuity from current weather
|
|
16023
|
-
// Weather tends to be similar for a few days with gradual changes
|
|
16024
|
-
let conditionId;
|
|
16025
|
-
if (i === 0) {
|
|
16026
|
-
// Tomorrow's weather has 60% chance of being similar to today
|
|
16027
|
-
conditionId =
|
|
16028
|
-
this.getRandomInt(1, 10) <= 6
|
|
16029
|
-
? currentCondition
|
|
16030
|
-
: ['sunny', 'partlyCloudy', 'cloudy', 'rain', 'snow'][this.getRandomInt(0, 4)];
|
|
16031
|
-
}
|
|
16032
|
-
else {
|
|
16033
|
-
// Subsequent days have 70% chance of being similar to previous day
|
|
16034
|
-
const previousCondition = forecast[i - 1]?.condition || currentCondition;
|
|
16035
|
-
conditionId =
|
|
16036
|
-
this.getRandomInt(1, 10) <= 7
|
|
16037
|
-
? previousCondition
|
|
16038
|
-
: ['sunny', 'partlyCloudy', 'cloudy', 'rain', 'snow'][this.getRandomInt(0, 4)];
|
|
16039
|
-
}
|
|
16040
|
-
// Generate temperatures with realistic variance
|
|
16041
|
-
// Max is typically higher than current temperature
|
|
16042
|
-
// Min is typically lower than current temperature
|
|
16043
|
-
const tempVariance = this.getRandomInt(-3, 3); // Small day-to-day change
|
|
16044
|
-
const baseMaxC = currentTemp + this.getRandomInt(0, 5); // Max is higher than current
|
|
16045
|
-
const maxTempC = baseMaxC + tempVariance;
|
|
16046
|
-
const minTempC = maxTempC - this.getRandomInt(5, 10); // Min is lower than max
|
|
16047
|
-
const maxTempF = Math.round((maxTempC * 9) / 5 + 32);
|
|
16048
|
-
const minTempF = Math.round((minTempC * 9) / 5 + 32);
|
|
16049
|
-
return {
|
|
16050
|
-
date: dateStr,
|
|
16051
|
-
day: dayName,
|
|
16052
|
-
condition: conditionId,
|
|
16053
|
-
iconUrl: '', // Would be set from API
|
|
16054
|
-
maxTempC,
|
|
16055
|
-
maxTempF,
|
|
16056
|
-
minTempC,
|
|
16057
|
-
minTempF,
|
|
16058
|
-
};
|
|
16059
|
-
});
|
|
16060
|
-
return { ...data, forecast };
|
|
16061
|
-
}));
|
|
16062
|
-
}
|
|
16063
|
-
/**
|
|
16064
|
-
* Set the API key for the weather service
|
|
16065
|
-
* No-op in the mock implementation
|
|
16066
|
-
* @param key The API key
|
|
16067
|
-
*/
|
|
16068
|
-
setApiKey(key) {
|
|
16069
|
-
// No-op for mock service
|
|
16070
|
-
console.log('API key setting is ignored in mock weather service');
|
|
16071
|
-
}
|
|
16072
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiMockService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
16073
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiMockService }); }
|
|
16074
|
-
}
|
|
16075
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiMockService, decorators: [{
|
|
16076
|
-
type: Injectable
|
|
16077
|
-
}] });
|
|
16078
|
-
|
|
16079
|
-
const AXP_WEATHER_API_KEY = new InjectionToken('AXP_WEATHER_API_KEY', {
|
|
16080
|
-
providedIn: 'root',
|
|
16081
|
-
factory: () => {
|
|
16082
|
-
return '40281dc1e31749edb6c104828250604';
|
|
16083
|
-
},
|
|
16084
|
-
});
|
|
16085
|
-
|
|
16086
|
-
/**
|
|
16087
|
-
* Real Weather API Service
|
|
16088
|
-
* Fetches actual weather data from a weather API service
|
|
16089
|
-
*/
|
|
16090
|
-
class AXPWeatherApiService extends AXPWeatherApiAbstract {
|
|
16091
|
-
constructor() {
|
|
16092
|
-
super(...arguments);
|
|
16093
|
-
this.http = inject(HttpClient);
|
|
16094
|
-
// API configuration
|
|
16095
|
-
this.baseApiUrl = 'https://api.weatherapi.com/v1';
|
|
16096
|
-
this.apiKeyToken = inject(AXP_WEATHER_API_KEY, { optional: true });
|
|
16097
|
-
}
|
|
16098
|
-
/**
|
|
16099
|
-
* Set the API key for the weather service
|
|
16100
|
-
* @param key The API key
|
|
16101
|
-
*/
|
|
16102
|
-
setApiKey(key) {
|
|
16103
|
-
if (key && key.trim() !== '') {
|
|
16104
|
-
this.apiKeyToken = key;
|
|
16105
|
-
}
|
|
16106
|
-
}
|
|
16107
|
-
/**
|
|
16108
|
-
* Get current weather data for given location
|
|
16109
|
-
* @param options Weather request options
|
|
16110
|
-
* @returns Observable with weather data
|
|
16111
|
-
*/
|
|
16112
|
-
getWeather(options) {
|
|
16113
|
-
// If forecast is likely needed, we should fetch it all at once
|
|
16114
|
-
if (options.useMockData === false || options.useMockData === undefined) {
|
|
16115
|
-
// For real API, use the forecast endpoint which includes current data
|
|
16116
|
-
return this.getForecast({
|
|
16117
|
-
...options,
|
|
16118
|
-
days: 1, // Request minimal forecast data
|
|
16119
|
-
});
|
|
16120
|
-
}
|
|
16121
|
-
// For mock data, we'll continue using separate calls for backward compatibility
|
|
16122
|
-
const locationParts = this.parseLocation(options.location || '');
|
|
16123
|
-
const displayName = this.formatLocationName(locationParts);
|
|
16124
|
-
// Use real API with query based on city only
|
|
16125
|
-
const query = encodeURIComponent(locationParts.city);
|
|
16126
|
-
const url = `${this.baseApiUrl}/current.json?key=${this.apiKeyToken}&q=${query}&aqi=no`;
|
|
16127
|
-
return this.http.get(url).pipe(map((data) => this.transformApiResponse(data, displayName)), catchError((error) => {
|
|
16128
|
-
console.error('Weather API error:', error);
|
|
16129
|
-
// Check for location not found error
|
|
16130
|
-
if (error.error?.error?.code === 1006) {
|
|
16131
|
-
return throwError(() => new Error(`Location "${locationParts.city}" not found. Please check the city name and try again.`));
|
|
16132
|
-
}
|
|
16133
|
-
return throwError(() => new Error(`Failed to fetch weather data: ${error.message}`));
|
|
16134
|
-
}));
|
|
16135
|
-
}
|
|
16136
|
-
/**
|
|
16137
|
-
* Get weather forecast for a location
|
|
16138
|
-
* @param options Weather request options with days
|
|
16139
|
-
* @returns Observable of weather data with forecast
|
|
16140
|
-
*/
|
|
16141
|
-
getForecast(options) {
|
|
16142
|
-
const locationParts = this.parseLocation(options.location || '');
|
|
16143
|
-
const displayName = this.formatLocationName(locationParts);
|
|
16144
|
-
const days = options.days || 5;
|
|
16145
|
-
// Build forecast API URL
|
|
16146
|
-
const query = encodeURIComponent(locationParts.city);
|
|
16147
|
-
const url = `${this.baseApiUrl}/forecast.json?key=${this.apiKeyToken}&q=${query}&days=${days}&aqi=no`;
|
|
16148
|
-
return this.http.get(url).pipe(map((data) => this.transformForecastResponse(data, displayName, days)), catchError((error) => {
|
|
16149
|
-
console.error('Weather API error:', error);
|
|
16150
|
-
// Check for location not found error
|
|
16151
|
-
if (error.error?.error?.code === 1006) {
|
|
16152
|
-
return throwError(() => new Error(`Location "${locationParts.city}" not found. Please check the city name and try again.`));
|
|
16153
|
-
}
|
|
16154
|
-
return throwError(() => new Error(`Failed to fetch forecast data: ${error.message}`));
|
|
16155
|
-
}));
|
|
16156
|
-
}
|
|
16157
|
-
/**
|
|
16158
|
-
* Transform API response to our internal data model
|
|
16159
|
-
* @param apiData Raw API response
|
|
16160
|
-
* @param displayName Formatted location name
|
|
16161
|
-
* @returns Normalized weather data
|
|
16162
|
-
*/
|
|
16163
|
-
transformApiResponse(apiData, displayName) {
|
|
16164
|
-
// Map API condition text to our condition IDs
|
|
16165
|
-
const conditionText = apiData.current?.condition?.text || '';
|
|
16166
|
-
const conditionId = this.mapApiConditionToId(conditionText);
|
|
16167
|
-
return {
|
|
16168
|
-
location: {
|
|
16169
|
-
city: apiData.location?.name || '',
|
|
16170
|
-
country: apiData.location?.country || '',
|
|
16171
|
-
displayName: displayName,
|
|
16172
|
-
},
|
|
16173
|
-
current: {
|
|
16174
|
-
condition: conditionId, // Use our mapped condition ID
|
|
16175
|
-
conditionCode: apiData.current?.condition?.code || 0,
|
|
16176
|
-
iconUrl: apiData.current?.condition?.icon || '',
|
|
16177
|
-
tempC: apiData.current?.temp_c || 0,
|
|
16178
|
-
tempF: apiData.current?.temp_f || 0,
|
|
16179
|
-
feelsLikeC: apiData.current?.feelslike_c || 0,
|
|
16180
|
-
feelsLikeF: apiData.current?.feelslike_f || 0,
|
|
16181
|
-
humidity: apiData.current?.humidity || 0,
|
|
16182
|
-
windKph: apiData.current?.wind_kph || 0,
|
|
16183
|
-
windMph: apiData.current?.wind_mph || 0,
|
|
16184
|
-
windDirection: apiData.current?.wind_dir || '',
|
|
16185
|
-
uv: apiData.current?.uv || 0,
|
|
16186
|
-
lastUpdated: apiData.current?.last_updated || new Date().toISOString(),
|
|
16187
|
-
},
|
|
16188
|
-
forecast: [],
|
|
16189
|
-
};
|
|
16190
|
-
}
|
|
16191
|
-
/**
|
|
16192
|
-
* Transform forecast API response to our internal data model
|
|
16193
|
-
* @param apiData Raw API response with forecast
|
|
16194
|
-
* @param displayName Formatted location name
|
|
16195
|
-
* @param days Number of forecast days
|
|
16196
|
-
* @returns Normalized weather data with forecast
|
|
16197
|
-
*/
|
|
16198
|
-
transformForecastResponse(apiData, displayName, days) {
|
|
16199
|
-
// First transform the current weather data using the current field of the forecast response
|
|
16200
|
-
const weatherData = this.transformApiResponse(apiData, displayName);
|
|
16201
|
-
// Then add the forecast data
|
|
16202
|
-
const forecastDays = apiData.forecast?.forecastday || [];
|
|
16203
|
-
weatherData.forecast = forecastDays.slice(0, days).map((day) => {
|
|
16204
|
-
// Parse the date for day name
|
|
16205
|
-
const date = new Date(day.date);
|
|
16206
|
-
const dayName = date.toLocaleDateString('en-US', { weekday: 'short' });
|
|
16207
|
-
// Map condition to our internal ID
|
|
16208
|
-
const conditionText = day.day?.condition?.text || '';
|
|
16209
|
-
const conditionId = this.mapApiConditionToId(conditionText);
|
|
16210
|
-
return {
|
|
16211
|
-
date: day.date,
|
|
16212
|
-
day: dayName,
|
|
16213
|
-
condition: conditionId,
|
|
16214
|
-
iconUrl: day.day?.condition?.icon || '',
|
|
16215
|
-
maxTempC: day.day?.maxtemp_c || 0,
|
|
16216
|
-
maxTempF: day.day?.maxtemp_f || 0,
|
|
16217
|
-
minTempC: day.day?.mintemp_c || 0,
|
|
16218
|
-
minTempF: day.day?.mintemp_f || 0,
|
|
16219
|
-
};
|
|
16220
|
-
});
|
|
16221
|
-
return weatherData;
|
|
16222
|
-
}
|
|
16223
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
16224
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiService }); }
|
|
16225
|
-
}
|
|
16226
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherApiService, decorators: [{
|
|
16227
|
-
type: Injectable
|
|
16228
|
-
}] });
|
|
16229
|
-
|
|
16230
|
-
/**
|
|
16231
|
-
* Weather Widget Component
|
|
16232
|
-
* Displays current weather conditions and optional forecast data
|
|
16233
|
-
* for a specified location with customizable display options.
|
|
16234
|
-
*/
|
|
16235
|
-
class AXPWeatherWidgetViewComponent extends AXPValueWidgetComponent {
|
|
16236
|
-
/**
|
|
16237
|
-
* Component constructor
|
|
16238
|
-
* @param cdr ChangeDetectorRef for triggering view updates
|
|
16239
|
-
*/
|
|
16240
|
-
constructor(cdr) {
|
|
16241
|
-
super();
|
|
16242
|
-
this.cdr = cdr;
|
|
16243
|
-
// Container element reference
|
|
16244
|
-
this.containerEl = viewChild.required('containerElement');
|
|
16245
|
-
// Weather API service instance
|
|
16246
|
-
this.weatherService = inject(AXPWeatherApiAbstract);
|
|
16247
|
-
// State signals
|
|
16248
|
-
this.weatherData = signal(null);
|
|
16249
|
-
this.isLoading = signal(true);
|
|
16250
|
-
this.hasError = signal(false);
|
|
16251
|
-
this.errorMessage = signal('');
|
|
16252
|
-
this.isForecastLoading = signal(false);
|
|
16253
|
-
// Auto-refresh subscription
|
|
16254
|
-
this.refreshSubscription = null;
|
|
16255
|
-
// Option-derived computed properties
|
|
16256
|
-
this.city = computed(() => {
|
|
16257
|
-
return this.options()['city'] || 'Newyork';
|
|
16258
|
-
});
|
|
16259
|
-
this.temperatureUnit = computed(() => this.options()['temperatureUnit']?.id || '°C');
|
|
16260
|
-
this.windSpeedUnit = computed(() => this.options()['windSpeedUnit']?.id || 'km/h');
|
|
16261
|
-
// Display option flags
|
|
16262
|
-
this.showCurrentCondition = computed(() => this.options()['showCurrentCondition'] !== false);
|
|
16263
|
-
this.showTemperature = computed(() => this.options()['showTemperature'] !== false);
|
|
16264
|
-
this.showHumidity = computed(() => this.options()['showHumidity'] !== false);
|
|
16265
|
-
this.showWind = computed(() => this.options()['showWind'] !== false);
|
|
16266
|
-
this.showForecast = computed(() => this.options()['showForecast'] !== false);
|
|
16267
|
-
this.forecastDays = computed(() => this.options()['forecastDays'] ?? 5);
|
|
16268
|
-
// Refresh settings
|
|
16269
|
-
this.autoRefresh = computed(() => this.options()['autoRefresh'] !== false);
|
|
16270
|
-
this.refreshInterval = computed(() => this.options()['refreshInterval']?.id ?? 15);
|
|
16271
|
-
// Reactivity effects
|
|
16272
|
-
this.optionsEffect = effect(() => {
|
|
16273
|
-
const opts = this.options();
|
|
16274
|
-
this.loadWeatherData();
|
|
16275
|
-
this.setupRefreshTimer();
|
|
16276
|
-
});
|
|
16277
|
-
this.valueEffect = effect(() => {
|
|
16278
|
-
this.city();
|
|
16279
|
-
this.loadWeatherData();
|
|
16280
|
-
});
|
|
16281
|
-
this.displayedForecast = computed(() => {
|
|
16282
|
-
const weatherData = this.weatherData();
|
|
16283
|
-
if (!weatherData?.forecast)
|
|
16284
|
-
return [];
|
|
16285
|
-
const forecastDays = this.forecastDays();
|
|
16286
|
-
return weatherData.forecast.slice(0, forecastDays);
|
|
16287
|
-
});
|
|
16288
|
-
// Inject the abstract service which will resolve to either the mock or real implementation
|
|
16289
|
-
this.weatherService = inject(AXPWeatherApiAbstract);
|
|
16290
|
-
setTimeout(() => {
|
|
16291
|
-
this.loadWeatherData();
|
|
16292
|
-
this.setupRefreshTimer();
|
|
16293
|
-
}, 0);
|
|
16294
|
-
}
|
|
16295
|
-
/**
|
|
16296
|
-
* Component cleanup on destroy
|
|
16297
|
-
*/
|
|
16298
|
-
ngOnDestroy() {
|
|
16299
|
-
this.clearRefreshTimer();
|
|
16300
|
-
}
|
|
16301
|
-
/**
|
|
16302
|
-
* Loads weather data from the API
|
|
16303
|
-
* Sets loading state and handles errors
|
|
16304
|
-
*/
|
|
16305
|
-
loadWeatherData() {
|
|
16306
|
-
this.isLoading.set(true);
|
|
16307
|
-
this.hasError.set(false);
|
|
16308
|
-
this.errorMessage.set('');
|
|
16309
|
-
const locationQuery = this.city();
|
|
16310
|
-
const shouldShowForecast = this.showForecast();
|
|
16311
|
-
// If we need forecast, request it directly to avoid duplicate calls
|
|
16312
|
-
// The forecast endpoint also returns current weather
|
|
16313
|
-
if (shouldShowForecast) {
|
|
16314
|
-
const forecastOptions = {
|
|
16315
|
-
location: locationQuery,
|
|
16316
|
-
tempUnit: this.temperatureUnit(),
|
|
16317
|
-
useMockData: false, // Use real API, signals to use forecast endpoint
|
|
16318
|
-
days: this.forecastDays(),
|
|
16319
|
-
};
|
|
16320
|
-
this.isForecastLoading.set(true);
|
|
16321
|
-
this.weatherService.getForecast(forecastOptions).subscribe({
|
|
16322
|
-
next: (data) => {
|
|
16323
|
-
this.weatherData.set(data);
|
|
16324
|
-
this.isLoading.set(false);
|
|
16325
|
-
this.isForecastLoading.set(false);
|
|
16326
|
-
setTimeout(() => this.cdr.detectChanges());
|
|
16327
|
-
},
|
|
16328
|
-
error: (error) => {
|
|
16329
|
-
this.hasError.set(true);
|
|
16330
|
-
this.errorMessage.set(error.message || 'Failed to load weather data. Please try again.');
|
|
16331
|
-
this.isLoading.set(false);
|
|
16332
|
-
this.isForecastLoading.set(false);
|
|
16333
|
-
this.cdr.detectChanges();
|
|
16334
|
-
},
|
|
16335
|
-
});
|
|
16336
|
-
return;
|
|
16337
|
-
}
|
|
16338
|
-
// If we only need current weather, use getWeather
|
|
16339
|
-
const requestOptions = {
|
|
16340
|
-
location: locationQuery,
|
|
16341
|
-
tempUnit: this.temperatureUnit(),
|
|
16342
|
-
useMockData: false, // Use real API
|
|
16343
|
-
};
|
|
16344
|
-
this.weatherService.getWeather(requestOptions).subscribe({
|
|
16345
|
-
next: (data) => {
|
|
16346
|
-
this.weatherData.set(data);
|
|
16347
|
-
this.isLoading.set(false);
|
|
16348
|
-
setTimeout(() => this.cdr.detectChanges());
|
|
16349
|
-
},
|
|
16350
|
-
error: (error) => {
|
|
16351
|
-
this.hasError.set(true);
|
|
16352
|
-
this.errorMessage.set(error.message || 'Failed to load weather data. Please try again.');
|
|
16353
|
-
this.isLoading.set(false);
|
|
16354
|
-
this.cdr.detectChanges();
|
|
16355
|
-
},
|
|
16356
|
-
});
|
|
16357
|
-
}
|
|
16358
|
-
/**
|
|
16359
|
-
* Sets up the auto-refresh timer based on configuration
|
|
16360
|
-
*/
|
|
16361
|
-
setupRefreshTimer() {
|
|
16362
|
-
this.clearRefreshTimer();
|
|
16363
|
-
if (!this.autoRefresh())
|
|
16364
|
-
return;
|
|
16365
|
-
const minInterval = 5; // 5 minutes minimum
|
|
16366
|
-
const intervalValue = Math.max(this.refreshInterval(), minInterval);
|
|
16367
|
-
const intervalMs = intervalValue * 60 * 1000;
|
|
16368
|
-
this.refreshSubscription = interval(intervalMs)
|
|
16369
|
-
.pipe(switchMap(() => {
|
|
16370
|
-
// Always use the loadWeatherData method to determine the correct API call
|
|
16371
|
-
this.loadWeatherData();
|
|
16372
|
-
// Return an empty observable to satisfy the switchMap typing
|
|
16373
|
-
return new Observable((observer) => {
|
|
16374
|
-
observer.complete();
|
|
16375
|
-
});
|
|
16376
|
-
}))
|
|
16377
|
-
.subscribe();
|
|
16378
|
-
}
|
|
16379
|
-
/**
|
|
16380
|
-
* Clears the refresh timer subscription
|
|
16381
|
-
*/
|
|
16382
|
-
clearRefreshTimer() {
|
|
16383
|
-
if (this.refreshSubscription) {
|
|
16384
|
-
this.refreshSubscription.unsubscribe();
|
|
16385
|
-
this.refreshSubscription = null;
|
|
16386
|
-
}
|
|
16387
|
-
}
|
|
16388
|
-
/**
|
|
16389
|
-
* Manually refreshes the weather data
|
|
16390
|
-
*/
|
|
16391
|
-
refreshWeather() {
|
|
16392
|
-
this.loadWeatherData();
|
|
16393
|
-
}
|
|
16394
|
-
/**
|
|
16395
|
-
* Gets the current temperature based on selected unit
|
|
16396
|
-
*/
|
|
16397
|
-
getCurrentTemperature() {
|
|
16398
|
-
if (!this.weatherData())
|
|
16399
|
-
return 0;
|
|
16400
|
-
return this.temperatureUnit() === '°C' ? this.weatherData().current.tempC : this.weatherData().current.tempF;
|
|
16401
|
-
}
|
|
16402
|
-
/**
|
|
16403
|
-
* Gets the feels like temperature based on selected unit
|
|
16404
|
-
*/
|
|
16405
|
-
getFeelsLikeTemperature() {
|
|
16406
|
-
if (!this.weatherData())
|
|
16407
|
-
return 0;
|
|
16408
|
-
return this.temperatureUnit() === '°C'
|
|
16409
|
-
? this.weatherData().current.feelsLikeC
|
|
16410
|
-
: this.weatherData().current.feelsLikeF;
|
|
16411
|
-
}
|
|
16412
|
-
/**
|
|
16413
|
-
* Gets the humidity percentage
|
|
16414
|
-
*/
|
|
16415
|
-
getHumidity() {
|
|
16416
|
-
return this.weatherData()?.current.humidity || 0;
|
|
16417
|
-
}
|
|
16418
|
-
/**
|
|
16419
|
-
* Gets the wind speed based on selected unit
|
|
16420
|
-
*/
|
|
16421
|
-
getWindSpeed() {
|
|
16422
|
-
if (!this.weatherData())
|
|
16423
|
-
return 0;
|
|
16424
|
-
return this.windSpeedUnit() === 'km/h' ? this.weatherData().current.windKph : this.weatherData().current.windMph;
|
|
16425
|
-
}
|
|
16426
|
-
/**
|
|
16427
|
-
* Gets the current weather condition
|
|
16428
|
-
*/
|
|
16429
|
-
getCurrentCondition() {
|
|
16430
|
-
const conditionId = this.weatherData()?.current.condition || '';
|
|
16431
|
-
return this.getConditionName(conditionId);
|
|
16432
|
-
}
|
|
16433
|
-
/**
|
|
16434
|
-
* Gets the formatted last updated time
|
|
16435
|
-
*/
|
|
16436
|
-
getLastUpdated() {
|
|
16437
|
-
if (!this.weatherData()?.current.lastUpdated)
|
|
16438
|
-
return '';
|
|
16439
|
-
return new Date(this.weatherData().current.lastUpdated).toLocaleTimeString();
|
|
16440
|
-
}
|
|
16441
|
-
/**
|
|
16442
|
-
* Gets the icon class for a weather condition
|
|
16443
|
-
* @param conditionId Weather condition ID
|
|
16444
|
-
* @returns Font Awesome icon class
|
|
16445
|
-
*/
|
|
16446
|
-
getConditionIcon(conditionId) {
|
|
16447
|
-
const condition = this.weatherService.getCondition(conditionId);
|
|
16448
|
-
return condition?.icon || 'fa-solid fa-question';
|
|
16449
|
-
}
|
|
16450
|
-
/**
|
|
16451
|
-
* Gets the display name for a weather condition
|
|
16452
|
-
* @param conditionId Weather condition ID
|
|
16453
|
-
* @returns Condition display name
|
|
16454
|
-
*/
|
|
16455
|
-
getConditionName(conditionId) {
|
|
16456
|
-
const condition = this.weatherService.getCondition(conditionId);
|
|
16457
|
-
return condition?.name || 'Unknown';
|
|
16458
|
-
}
|
|
16459
|
-
/**
|
|
16460
|
-
* Gets the color for a weather condition
|
|
16461
|
-
* @param conditionId Weather condition ID
|
|
16462
|
-
* @returns CSS color value
|
|
16463
|
-
*/
|
|
16464
|
-
getConditionColor(conditionId) {
|
|
16465
|
-
const condition = this.weatherService.getCondition(conditionId);
|
|
16466
|
-
return condition?.color || '#999999';
|
|
16467
|
-
}
|
|
16468
|
-
/**
|
|
16469
|
-
* Cleans up chart resources
|
|
16470
|
-
* Required by AXPChartBaseComponent
|
|
16471
|
-
*/
|
|
16472
|
-
cleanupChart() {
|
|
16473
|
-
this.clearRefreshTimer();
|
|
16474
|
-
}
|
|
16475
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherWidgetViewComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
16476
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: AXPWeatherWidgetViewComponent, isStandalone: true, selector: "ng-component", providers: [
|
|
16477
|
-
{
|
|
16478
|
-
provide: AXPWeatherApiAbstract,
|
|
16479
|
-
useClass: AXPWeatherApiService,
|
|
16480
|
-
},
|
|
16481
|
-
], viewQueries: [{ propertyName: "containerEl", first: true, predicate: ["containerElement"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- Weather Widget Component Template -->\n<div class=\"axp-weather-container\" #containerElement>\n <!-- Loading indicator -->\n @if (isLoading()) {\n <div class=\"axp-weather-loading-overlay\">\n <div class=\"axp-weather-loading-spinner\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading weather data...</span>\n </div>\n </div>\n }\n\n <!-- Error message -->\n @if (hasError()) {\n <div class=\"axp-weather-error-overlay\">\n <div class=\"axp-weather-error-message\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ errorMessage() }}</span>\n <button class=\"axp-weather-retry-button\" (click)=\"refreshWeather()\">\n <i class=\"fa-solid fa-sync-alt\"></i>\n <span>Retry</span>\n </button>\n </div>\n </div>\n }\n\n <!-- Weather content - only show when we have data -->\n @if (weatherData()) {\n <!-- Background decorations based on weather condition -->\n <div class=\"axp-weather-background-decorations\">\n <div class=\"axp-weather-decoration\" [ngClass]=\"weatherData()?.current?.condition?.toLowerCase()\"></div>\n <div class=\"axp-weather-gradient-overlay\"></div>\n </div>\n\n <div class=\"axp-weather-inner\">\n <!-- Location information section -->\n <div class=\"axp-weather-location-info\">\n <div class=\"axp-weather-location-icon\">\n <i class=\"fa-solid fa-location-dot\"></i>\n </div>\n <div class=\"axp-weather-location-text\">\n <h2 class=\"axp-weather-location-name\">{{ city() }}</h2>\n </div>\n </div>\n\n <!-- Current weather conditions section -->\n <div class=\"axp-weather-current-weather\">\n <!-- Weather icon and condition name -->\n @if (showCurrentCondition()) {\n <div class=\"axp-weather-condition\">\n <div class=\"axp-weather-icon-wrapper\">\n <div class=\"axp-weather-icon\">\n <i\n [class]=\"getConditionIcon(weatherData()?.current?.condition || '')\"\n [style.color]=\"getConditionColor(weatherData()?.current?.condition || '')\"\n ></i>\n </div>\n <div\n class=\"axp-weather-icon-glow\"\n [style.background-color]=\"getConditionColor(weatherData()?.current?.condition || '')\"\n ></div>\n </div>\n <div class=\"axp-weather-condition-name\">{{ getCurrentCondition() }}</div>\n </div>\n }\n\n <!-- Temperature display -->\n @if (showTemperature()) {\n <div class=\"axp-weather-temperature\">\n <span class=\"axp-weather-temperature-value\">{{ getCurrentTemperature() }}</span>\n <span class=\"axp-weather-temperature-unit\">{{ temperatureUnit() }}</span>\n </div>\n }\n </div>\n\n <!-- Weather details section (humidity and wind) -->\n @if (showHumidity() || showWind()) {\n <div class=\"axp-weather-details\">\n <!-- Humidity information -->\n @if (showHumidity()) {\n <div class=\"axp-weather-detail-item\">\n <div class=\"axp-weather-detail-icon\">\n <i class=\"fa-solid fa-droplet\"></i>\n </div>\n <div class=\"axp-weather-detail-info\">\n <div class=\"axp-weather-detail-label\">Humidity</div>\n <div class=\"axp-weather-detail-value\">{{ getHumidity() }}%</div>\n </div>\n </div>\n }\n\n <!-- Wind speed information -->\n @if (showWind()) {\n <div class=\"axp-weather-detail-item\">\n <div class=\"axp-weather-detail-icon\">\n <i class=\"fa-solid fa-wind\"></i>\n </div>\n <div class=\"axp-weather-detail-info\">\n <div class=\"axp-weather-detail-label\">Wind</div>\n <div class=\"axp-weather-detail-value\">{{ getWindSpeed() }} {{ windSpeedUnit() }}</div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Weather forecast section -->\n @if (showForecast() && weatherData()?.forecast) {\n <div class=\"axp-weather-forecast\">\n <div class=\"axp-weather-forecast-header\">\n <h3 class=\"axp-weather-forecast-title\">\n <i class=\"fa-solid fa-calendar-days\"></i>\n <span>Forecast</span>\n </h3>\n <!-- <div class=\"axp-weather-scroll-indicator\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </div> -->\n </div>\n <!-- Loading indicator for forecast -->\n @if (isForecastLoading()) {\n <div class=\"axp-weather-forecast-loading\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading forecast...</span>\n </div>\n }\n <!-- Scrollable forecast days display -->\n <div class=\"axp-weather-forecast-items\">\n @for (day of displayedForecast(); track day.day) {\n <div class=\"axp-weather-forecast-day\">\n <div class=\"axp-weather-forecast-day-name\">{{ day.day }}</div>\n <div class=\"axp-weather-forecast-icon\" title=\"{{ getConditionName(day.condition) }}\">\n <i [class]=\"getConditionIcon(day.condition)\" [style.color]=\"getConditionColor(day.condition)\"></i>\n </div>\n <div class=\"axp-weather-forecast-temps\">\n <span class=\"axp-weather-forecast-low\">\n {{ temperatureUnit() === '\u00B0C' ? day.minTempC : day.minTempF }}\u00B0\n </span>\n <span class=\"axp-weather-forecast-high\">\n {{ temperatureUnit() === '\u00B0C' ? day.maxTempC : day.maxTempF }}\u00B0\n </span>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Last updated timestamp -->\n <div class=\"axp-weather-last-updated\">\n <span>Last updated: {{ getLastUpdated() }}</span>\n </div>\n\n <!-- Manual refresh button -->\n <div class=\"axp-weather-refresh-action\">\n <button\n class=\"axp-weather-refresh-button\"\n (click)=\"refreshWeather()\"\n [attr.aria-label]=\"'Refresh weather data'\"\n title=\"Refresh weather data\"\n >\n <i class=\"fa-solid fa-sync-alt\"></i>\n <span>Refresh</span>\n </button>\n </div>\n </div>\n } @else if (!isLoading() && !hasError()) {\n <!-- No data state (not loading, no error) -->\n <div class=\"axp-weather-no-data-state\">\n <i class=\"fa-solid fa-cloud-sun\"></i>\n <p>No weather data available</p>\n <button class=\"axp-weather-refresh-button\" (click)=\"refreshWeather()\">\n <i class=\"fa-solid fa-sync-alt\"></i>\n <span>Load Data</span>\n </button>\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%;--primary-gradient-start: #2196f3;--primary-gradient-end: #1976d2;--shadow-color: rgba(0, 0, 0, .2);--glass-bg: rgba(255, 255, 255, .15);--glass-border: rgba(255, 255, 255, .2);--text-primary: rgba(255, 255, 255, .95);--text-secondary: rgba(255, 255, 255, .75);--transition-speed: .3s}.axp-weather-container{width:100%;height:100%;border-radius:8px;overflow:hidden;position:relative;box-shadow:0 4px 8px rgba(0,0,0,.1);color:#fff;min-height:300px;background-color:#2c3e50}.axp-weather-loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(44,62,80,.85);display:flex;justify-content:center;align-items:center;z-index:100}.axp-weather-loading-spinner{text-align:center}.axp-weather-loading-spinner i{font-size:2.5rem;color:#fff;margin-bottom:.5rem}.axp-weather-loading-spinner span{display:block;color:#fff;font-size:1rem}.axp-weather-error-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(189,54,47,.85);display:flex;justify-content:center;align-items:center;z-index:100}.axp-weather-error-message{text-align:center;padding:1rem}.axp-weather-error-message i{font-size:2.5rem;color:#fff;margin-bottom:.5rem}.axp-weather-error-message span{display:block;color:#fff;font-size:1.1rem;margin-bottom:1rem}.axp-weather-error-message .axp-weather-retry-button{color:#bd362f;border:none;border-radius:4px;padding:.5rem 1rem;font-size:1rem;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:.5rem}.axp-weather-error-message .axp-weather-retry-button:hover{transform:translateY(-1px)}.axp-weather-no-data-state{display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:2rem;text-align:center}.axp-weather-no-data-state i{font-size:3rem;margin-bottom:1rem;color:rgba(255,255,255,.8)}.axp-weather-no-data-state p{margin-bottom:1.5rem;font-size:1.1rem}.axp-weather-no-data-state .axp-weather-refresh-button{background-color:rgba(255,255,255,.2);color:#fff;border:none;border-radius:4px;padding:.5rem 1rem;font-size:1rem;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:.5rem}.axp-weather-no-data-state .axp-weather-refresh-button:hover{background-color:rgba(255,255,255,.3);transform:translateY(-1px)}.axp-weather-background-decorations{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.axp-weather-decoration{position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-position:center}.axp-weather-decoration.sunny{background:linear-gradient(135deg,#ff7e00,#f7d358)}.axp-weather-decoration.partlyCloudy{background:linear-gradient(135deg,#7ba2e7,#b4d2f7)}.axp-weather-decoration.cloudy{background:linear-gradient(135deg,#717e8c,#919eab)}.axp-weather-decoration.rain{background:linear-gradient(135deg,#6a8caf,#567a9e)}.axp-weather-decoration.snow{background:linear-gradient(135deg,#99b3cc,#c6d4e1)}.axp-weather-decoration.thunder{background:linear-gradient(135deg,#425777,#2c3e50)}.axp-weather-decoration.mist{background:linear-gradient(135deg,#94a3b8,#cbd5e1)}.axp-weather-gradient-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.5))}.axp-weather-inner{position:relative;z-index:2;height:100%;padding:1.5rem;display:flex;flex-direction:column}.axp-weather-location-info{display:flex;align-items:center;margin-bottom:1.5rem}.axp-weather-location-icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background-color:rgba(255,255,255,.2);border-radius:50%;margin-right:.75rem}.axp-weather-location-icon i{color:#fff}.axp-weather-location-name{margin:0;font-size:1.5rem;font-weight:500;text-shadow:1px 1px 3px rgba(0,0,0,.2);text-transform:capitalize}.axp-weather-current-weather{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;flex-wrap:wrap}.axp-weather-condition{display:flex;flex-direction:column;align-items:center}.axp-weather-icon-wrapper{position:relative;margin-bottom:.5rem}.axp-weather-icon{position:relative;z-index:2}.axp-weather-icon i{font-size:2.75rem;filter:drop-shadow(0 0 8px rgba(0,0,0,.3))}.axp-weather-icon-glow{position:absolute;z-index:1;top:50%;left:50%;transform:translate(-50%,-50%);width:45px;height:45px;border-radius:50%;filter:blur(15px);opacity:.75}.axp-weather-condition-name{font-size:1.1rem;text-align:center;text-shadow:1px 1px 2px rgba(0,0,0,.2)}.axp-weather-temperature{display:flex;align-items:flex-start;text-shadow:1px 1px 3px rgba(0,0,0,.3)}.axp-weather-temperature-value{font-size:3.5rem;font-weight:500;line-height:1}.axp-weather-temperature-unit{font-size:1.5rem;margin-top:.25rem}.axp-weather-details{display:flex;flex-wrap:wrap;gap:1.5rem;margin-bottom:1.5rem;padding:1rem;background-color:rgba(0,0,0,.15);border-radius:8px}.axp-weather-detail-item{display:flex;align-items:center;flex:1;min-width:120px}.axp-weather-detail-icon{width:40px;height:40px;border-radius:50%;background-color:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;margin-right:.75rem}.axp-weather-detail-icon i{font-size:1.25rem}.axp-weather-detail-info{display:flex;flex-direction:column}.axp-weather-detail-label{font-size:.875rem;color:rgba(255,255,255,.8);margin-bottom:.25rem}.axp-weather-detail-value{font-size:1.125rem;font-weight:500}.axp-weather-forecast{margin-top:auto;margin-bottom:1rem;background-color:rgba(0,0,0,.15);border-radius:8px;padding:1rem}.axp-weather-forecast-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem}.axp-weather-forecast-title{margin:0;font-size:1.125rem;font-weight:500;display:flex;align-items:center}.axp-weather-forecast-title i{margin-right:.5rem;opacity:.8}.axp-weather-scroll-indicator{color:rgba(255,255,255,.6)}.axp-weather-forecast-items{display:flex;overflow-x:auto;gap:.75rem;padding-bottom:.5rem}.axp-weather-forecast-items::-webkit-scrollbar{height:4px}.axp-weather-forecast-items::-webkit-scrollbar-thumb{background-color:rgba(255,255,255,.3);border-radius:4px}.axp-weather-forecast-items::-webkit-scrollbar-track{background-color:rgba(0,0,0,.1);border-radius:4px}.axp-weather-forecast-day{min-width:80px;display:flex;flex-direction:column;align-items:center;padding:.75rem .5rem;background-color:rgba(255,255,255,.1);border-radius:6px;transition:all .3s ease}.axp-weather-forecast-day:hover{background-color:rgba(255,255,255,.15);transform:translateY(-2px)}.axp-weather-forecast-day-name{font-size:.875rem;margin-bottom:.5rem;font-weight:500}.axp-weather-forecast-icon{font-size:1.5rem;margin-bottom:.5rem}.axp-weather-forecast-icon i{filter:drop-shadow(0 0 5px rgba(0,0,0,.2))}.axp-weather-forecast-temps{display:flex;flex-direction:row;gap:.75rem;align-items:center;font-size:.875rem}.axp-weather-last-updated{text-align:center;font-size:.75rem;opacity:.7;margin-bottom:.5rem}.axp-weather-refresh-action{text-align:center}.axp-weather-refresh-button{background-color:rgba(255,255,255,.2);color:#fff;border:none;border-radius:4px;padding:.5rem 1rem;font-size:.875rem;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:.5rem}.axp-weather-refresh-button:hover{background-color:rgba(255,255,255,.3);transform:translateY(-1px)}.axp-weather-refresh-button i{font-size:.875rem}@media (max-width: 576px){.axp-weather-inner{padding:.8rem;gap:.4rem}.axp-weather-location-name{font-size:1.1rem}.axp-weather-temperature{font-size:2.5rem}.axp-weather-temperature-unit{font-size:1.1rem}.axp-weather-weather-details{flex-direction:column;gap:.5rem}.axp-weather-forecast-items{flex-wrap:nowrap;overflow-x:auto;padding-bottom:.5rem;margin:0 -.4rem;scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scrollbar-width:thin;mask-image:linear-gradient(to right,#000 95%,rgba(0,0,0,0));-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 95%,rgba(0,0,0,0))}.axp-weather-forecast-items::-webkit-scrollbar{height:4px}.axp-weather-forecast-items::-webkit-scrollbar-thumb{background-color:rgba(255,255,255,.2);border-radius:4px}.axp-weather-forecast-day{min-width:60px;flex:0 0 auto;padding:.4rem .6rem}.axp-weather-forecast-day-name{font-size:.7rem}.axp-weather-forecast-icon{padding:.3rem;height:1.6rem}.axp-weather-forecast-temps{font-size:.7rem}.axp-weather-scroll-indicator{display:block}}:host-context(.theme-dark){--glass-bg: rgba(0, 0, 0, .3);--glass-border: rgba(255, 255, 255, .1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: AXDateTimeModule }, { kind: "ngmodule", type: AXFormatModule }, { kind: "ngmodule", type: HttpClientModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
16482
|
-
}
|
|
16483
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: AXPWeatherWidgetViewComponent, decorators: [{
|
|
16484
|
-
type: Component,
|
|
16485
|
-
args: [{ standalone: true, imports: [CommonModule, AXDateTimeModule, AXFormatModule, HttpClientModule], providers: [
|
|
16486
|
-
{
|
|
16487
|
-
provide: AXPWeatherApiAbstract,
|
|
16488
|
-
useClass: AXPWeatherApiService,
|
|
16489
|
-
},
|
|
16490
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Weather Widget Component Template -->\n<div class=\"axp-weather-container\" #containerElement>\n <!-- Loading indicator -->\n @if (isLoading()) {\n <div class=\"axp-weather-loading-overlay\">\n <div class=\"axp-weather-loading-spinner\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading weather data...</span>\n </div>\n </div>\n }\n\n <!-- Error message -->\n @if (hasError()) {\n <div class=\"axp-weather-error-overlay\">\n <div class=\"axp-weather-error-message\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ errorMessage() }}</span>\n <button class=\"axp-weather-retry-button\" (click)=\"refreshWeather()\">\n <i class=\"fa-solid fa-sync-alt\"></i>\n <span>Retry</span>\n </button>\n </div>\n </div>\n }\n\n <!-- Weather content - only show when we have data -->\n @if (weatherData()) {\n <!-- Background decorations based on weather condition -->\n <div class=\"axp-weather-background-decorations\">\n <div class=\"axp-weather-decoration\" [ngClass]=\"weatherData()?.current?.condition?.toLowerCase()\"></div>\n <div class=\"axp-weather-gradient-overlay\"></div>\n </div>\n\n <div class=\"axp-weather-inner\">\n <!-- Location information section -->\n <div class=\"axp-weather-location-info\">\n <div class=\"axp-weather-location-icon\">\n <i class=\"fa-solid fa-location-dot\"></i>\n </div>\n <div class=\"axp-weather-location-text\">\n <h2 class=\"axp-weather-location-name\">{{ city() }}</h2>\n </div>\n </div>\n\n <!-- Current weather conditions section -->\n <div class=\"axp-weather-current-weather\">\n <!-- Weather icon and condition name -->\n @if (showCurrentCondition()) {\n <div class=\"axp-weather-condition\">\n <div class=\"axp-weather-icon-wrapper\">\n <div class=\"axp-weather-icon\">\n <i\n [class]=\"getConditionIcon(weatherData()?.current?.condition || '')\"\n [style.color]=\"getConditionColor(weatherData()?.current?.condition || '')\"\n ></i>\n </div>\n <div\n class=\"axp-weather-icon-glow\"\n [style.background-color]=\"getConditionColor(weatherData()?.current?.condition || '')\"\n ></div>\n </div>\n <div class=\"axp-weather-condition-name\">{{ getCurrentCondition() }}</div>\n </div>\n }\n\n <!-- Temperature display -->\n @if (showTemperature()) {\n <div class=\"axp-weather-temperature\">\n <span class=\"axp-weather-temperature-value\">{{ getCurrentTemperature() }}</span>\n <span class=\"axp-weather-temperature-unit\">{{ temperatureUnit() }}</span>\n </div>\n }\n </div>\n\n <!-- Weather details section (humidity and wind) -->\n @if (showHumidity() || showWind()) {\n <div class=\"axp-weather-details\">\n <!-- Humidity information -->\n @if (showHumidity()) {\n <div class=\"axp-weather-detail-item\">\n <div class=\"axp-weather-detail-icon\">\n <i class=\"fa-solid fa-droplet\"></i>\n </div>\n <div class=\"axp-weather-detail-info\">\n <div class=\"axp-weather-detail-label\">Humidity</div>\n <div class=\"axp-weather-detail-value\">{{ getHumidity() }}%</div>\n </div>\n </div>\n }\n\n <!-- Wind speed information -->\n @if (showWind()) {\n <div class=\"axp-weather-detail-item\">\n <div class=\"axp-weather-detail-icon\">\n <i class=\"fa-solid fa-wind\"></i>\n </div>\n <div class=\"axp-weather-detail-info\">\n <div class=\"axp-weather-detail-label\">Wind</div>\n <div class=\"axp-weather-detail-value\">{{ getWindSpeed() }} {{ windSpeedUnit() }}</div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Weather forecast section -->\n @if (showForecast() && weatherData()?.forecast) {\n <div class=\"axp-weather-forecast\">\n <div class=\"axp-weather-forecast-header\">\n <h3 class=\"axp-weather-forecast-title\">\n <i class=\"fa-solid fa-calendar-days\"></i>\n <span>Forecast</span>\n </h3>\n <!-- <div class=\"axp-weather-scroll-indicator\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </div> -->\n </div>\n <!-- Loading indicator for forecast -->\n @if (isForecastLoading()) {\n <div class=\"axp-weather-forecast-loading\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading forecast...</span>\n </div>\n }\n <!-- Scrollable forecast days display -->\n <div class=\"axp-weather-forecast-items\">\n @for (day of displayedForecast(); track day.day) {\n <div class=\"axp-weather-forecast-day\">\n <div class=\"axp-weather-forecast-day-name\">{{ day.day }}</div>\n <div class=\"axp-weather-forecast-icon\" title=\"{{ getConditionName(day.condition) }}\">\n <i [class]=\"getConditionIcon(day.condition)\" [style.color]=\"getConditionColor(day.condition)\"></i>\n </div>\n <div class=\"axp-weather-forecast-temps\">\n <span class=\"axp-weather-forecast-low\">\n {{ temperatureUnit() === '\u00B0C' ? day.minTempC : day.minTempF }}\u00B0\n </span>\n <span class=\"axp-weather-forecast-high\">\n {{ temperatureUnit() === '\u00B0C' ? day.maxTempC : day.maxTempF }}\u00B0\n </span>\n </div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Last updated timestamp -->\n <div class=\"axp-weather-last-updated\">\n <span>Last updated: {{ getLastUpdated() }}</span>\n </div>\n\n <!-- Manual refresh button -->\n <div class=\"axp-weather-refresh-action\">\n <button\n class=\"axp-weather-refresh-button\"\n (click)=\"refreshWeather()\"\n [attr.aria-label]=\"'Refresh weather data'\"\n title=\"Refresh weather data\"\n >\n <i class=\"fa-solid fa-sync-alt\"></i>\n <span>Refresh</span>\n </button>\n </div>\n </div>\n } @else if (!isLoading() && !hasError()) {\n <!-- No data state (not loading, no error) -->\n <div class=\"axp-weather-no-data-state\">\n <i class=\"fa-solid fa-cloud-sun\"></i>\n <p>No weather data available</p>\n <button class=\"axp-weather-refresh-button\" (click)=\"refreshWeather()\">\n <i class=\"fa-solid fa-sync-alt\"></i>\n <span>Load Data</span>\n </button>\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%;--primary-gradient-start: #2196f3;--primary-gradient-end: #1976d2;--shadow-color: rgba(0, 0, 0, .2);--glass-bg: rgba(255, 255, 255, .15);--glass-border: rgba(255, 255, 255, .2);--text-primary: rgba(255, 255, 255, .95);--text-secondary: rgba(255, 255, 255, .75);--transition-speed: .3s}.axp-weather-container{width:100%;height:100%;border-radius:8px;overflow:hidden;position:relative;box-shadow:0 4px 8px rgba(0,0,0,.1);color:#fff;min-height:300px;background-color:#2c3e50}.axp-weather-loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(44,62,80,.85);display:flex;justify-content:center;align-items:center;z-index:100}.axp-weather-loading-spinner{text-align:center}.axp-weather-loading-spinner i{font-size:2.5rem;color:#fff;margin-bottom:.5rem}.axp-weather-loading-spinner span{display:block;color:#fff;font-size:1rem}.axp-weather-error-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(189,54,47,.85);display:flex;justify-content:center;align-items:center;z-index:100}.axp-weather-error-message{text-align:center;padding:1rem}.axp-weather-error-message i{font-size:2.5rem;color:#fff;margin-bottom:.5rem}.axp-weather-error-message span{display:block;color:#fff;font-size:1.1rem;margin-bottom:1rem}.axp-weather-error-message .axp-weather-retry-button{color:#bd362f;border:none;border-radius:4px;padding:.5rem 1rem;font-size:1rem;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:.5rem}.axp-weather-error-message .axp-weather-retry-button:hover{transform:translateY(-1px)}.axp-weather-no-data-state{display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:2rem;text-align:center}.axp-weather-no-data-state i{font-size:3rem;margin-bottom:1rem;color:rgba(255,255,255,.8)}.axp-weather-no-data-state p{margin-bottom:1.5rem;font-size:1.1rem}.axp-weather-no-data-state .axp-weather-refresh-button{background-color:rgba(255,255,255,.2);color:#fff;border:none;border-radius:4px;padding:.5rem 1rem;font-size:1rem;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:.5rem}.axp-weather-no-data-state .axp-weather-refresh-button:hover{background-color:rgba(255,255,255,.3);transform:translateY(-1px)}.axp-weather-background-decorations{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.axp-weather-decoration{position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-position:center}.axp-weather-decoration.sunny{background:linear-gradient(135deg,#ff7e00,#f7d358)}.axp-weather-decoration.partlyCloudy{background:linear-gradient(135deg,#7ba2e7,#b4d2f7)}.axp-weather-decoration.cloudy{background:linear-gradient(135deg,#717e8c,#919eab)}.axp-weather-decoration.rain{background:linear-gradient(135deg,#6a8caf,#567a9e)}.axp-weather-decoration.snow{background:linear-gradient(135deg,#99b3cc,#c6d4e1)}.axp-weather-decoration.thunder{background:linear-gradient(135deg,#425777,#2c3e50)}.axp-weather-decoration.mist{background:linear-gradient(135deg,#94a3b8,#cbd5e1)}.axp-weather-gradient-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.5))}.axp-weather-inner{position:relative;z-index:2;height:100%;padding:1.5rem;display:flex;flex-direction:column}.axp-weather-location-info{display:flex;align-items:center;margin-bottom:1.5rem}.axp-weather-location-icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background-color:rgba(255,255,255,.2);border-radius:50%;margin-right:.75rem}.axp-weather-location-icon i{color:#fff}.axp-weather-location-name{margin:0;font-size:1.5rem;font-weight:500;text-shadow:1px 1px 3px rgba(0,0,0,.2);text-transform:capitalize}.axp-weather-current-weather{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;flex-wrap:wrap}.axp-weather-condition{display:flex;flex-direction:column;align-items:center}.axp-weather-icon-wrapper{position:relative;margin-bottom:.5rem}.axp-weather-icon{position:relative;z-index:2}.axp-weather-icon i{font-size:2.75rem;filter:drop-shadow(0 0 8px rgba(0,0,0,.3))}.axp-weather-icon-glow{position:absolute;z-index:1;top:50%;left:50%;transform:translate(-50%,-50%);width:45px;height:45px;border-radius:50%;filter:blur(15px);opacity:.75}.axp-weather-condition-name{font-size:1.1rem;text-align:center;text-shadow:1px 1px 2px rgba(0,0,0,.2)}.axp-weather-temperature{display:flex;align-items:flex-start;text-shadow:1px 1px 3px rgba(0,0,0,.3)}.axp-weather-temperature-value{font-size:3.5rem;font-weight:500;line-height:1}.axp-weather-temperature-unit{font-size:1.5rem;margin-top:.25rem}.axp-weather-details{display:flex;flex-wrap:wrap;gap:1.5rem;margin-bottom:1.5rem;padding:1rem;background-color:rgba(0,0,0,.15);border-radius:8px}.axp-weather-detail-item{display:flex;align-items:center;flex:1;min-width:120px}.axp-weather-detail-icon{width:40px;height:40px;border-radius:50%;background-color:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;margin-right:.75rem}.axp-weather-detail-icon i{font-size:1.25rem}.axp-weather-detail-info{display:flex;flex-direction:column}.axp-weather-detail-label{font-size:.875rem;color:rgba(255,255,255,.8);margin-bottom:.25rem}.axp-weather-detail-value{font-size:1.125rem;font-weight:500}.axp-weather-forecast{margin-top:auto;margin-bottom:1rem;background-color:rgba(0,0,0,.15);border-radius:8px;padding:1rem}.axp-weather-forecast-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem}.axp-weather-forecast-title{margin:0;font-size:1.125rem;font-weight:500;display:flex;align-items:center}.axp-weather-forecast-title i{margin-right:.5rem;opacity:.8}.axp-weather-scroll-indicator{color:rgba(255,255,255,.6)}.axp-weather-forecast-items{display:flex;overflow-x:auto;gap:.75rem;padding-bottom:.5rem}.axp-weather-forecast-items::-webkit-scrollbar{height:4px}.axp-weather-forecast-items::-webkit-scrollbar-thumb{background-color:rgba(255,255,255,.3);border-radius:4px}.axp-weather-forecast-items::-webkit-scrollbar-track{background-color:rgba(0,0,0,.1);border-radius:4px}.axp-weather-forecast-day{min-width:80px;display:flex;flex-direction:column;align-items:center;padding:.75rem .5rem;background-color:rgba(255,255,255,.1);border-radius:6px;transition:all .3s ease}.axp-weather-forecast-day:hover{background-color:rgba(255,255,255,.15);transform:translateY(-2px)}.axp-weather-forecast-day-name{font-size:.875rem;margin-bottom:.5rem;font-weight:500}.axp-weather-forecast-icon{font-size:1.5rem;margin-bottom:.5rem}.axp-weather-forecast-icon i{filter:drop-shadow(0 0 5px rgba(0,0,0,.2))}.axp-weather-forecast-temps{display:flex;flex-direction:row;gap:.75rem;align-items:center;font-size:.875rem}.axp-weather-last-updated{text-align:center;font-size:.75rem;opacity:.7;margin-bottom:.5rem}.axp-weather-refresh-action{text-align:center}.axp-weather-refresh-button{background-color:rgba(255,255,255,.2);color:#fff;border:none;border-radius:4px;padding:.5rem 1rem;font-size:.875rem;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:.5rem}.axp-weather-refresh-button:hover{background-color:rgba(255,255,255,.3);transform:translateY(-1px)}.axp-weather-refresh-button i{font-size:.875rem}@media (max-width: 576px){.axp-weather-inner{padding:.8rem;gap:.4rem}.axp-weather-location-name{font-size:1.1rem}.axp-weather-temperature{font-size:2.5rem}.axp-weather-temperature-unit{font-size:1.1rem}.axp-weather-weather-details{flex-direction:column;gap:.5rem}.axp-weather-forecast-items{flex-wrap:nowrap;overflow-x:auto;padding-bottom:.5rem;margin:0 -.4rem;scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scrollbar-width:thin;mask-image:linear-gradient(to right,#000 95%,rgba(0,0,0,0));-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 95%,rgba(0,0,0,0))}.axp-weather-forecast-items::-webkit-scrollbar{height:4px}.axp-weather-forecast-items::-webkit-scrollbar-thumb{background-color:rgba(255,255,255,.2);border-radius:4px}.axp-weather-forecast-day{min-width:60px;flex:0 0 auto;padding:.4rem .6rem}.axp-weather-forecast-day-name{font-size:.7rem}.axp-weather-forecast-icon{padding:.3rem;height:1.6rem}.axp-weather-forecast-temps{font-size:.7rem}.axp-weather-scroll-indicator{display:block}}:host-context(.theme-dark){--glass-bg: rgba(0, 0, 0, .3);--glass-border: rgba(255, 255, 255, .1)}\n"] }]
|
|
16491
|
-
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
|
|
16492
|
-
|
|
16493
|
-
var weatherWidget_component = /*#__PURE__*/Object.freeze({
|
|
16494
|
-
__proto__: null,
|
|
16495
|
-
AXPWeatherWidgetViewComponent: AXPWeatherWidgetViewComponent
|
|
16496
|
-
});
|
|
16497
|
-
|
|
16498
|
-
/**
|
|
16499
|
-
* Weather Widget Configuration
|
|
16500
|
-
* Provides customization options for displaying weather data and forecast
|
|
16501
|
-
*/
|
|
16502
|
-
const AXPWeatherWidget = {
|
|
16503
|
-
name: 'weather',
|
|
16504
|
-
title: 'Weather Widget',
|
|
16505
|
-
categories: [AXP_WIDGETS_UTILITY_CATEGORY],
|
|
16506
|
-
groups: [AXPWidgetGroupEnum.DashboardWidget],
|
|
16507
|
-
type: 'dashboard',
|
|
16508
|
-
icon: 'fa-light fa-cloud-sun',
|
|
16509
|
-
properties: [
|
|
16510
|
-
/* Location Settings */
|
|
16511
|
-
{
|
|
16512
|
-
name: 'city',
|
|
16513
|
-
title: 'City',
|
|
16514
|
-
group: AXP_DATA_PROPERTY_GROUP,
|
|
16515
|
-
schema: {
|
|
16516
|
-
defaultValue: 'New York',
|
|
16517
|
-
dataType: 'string',
|
|
16518
|
-
interface: {
|
|
16519
|
-
name: 'city',
|
|
16520
|
-
path: 'options.city',
|
|
16521
|
-
type: AXPWidgetsCatalog.text,
|
|
16522
|
-
},
|
|
16523
|
-
},
|
|
16524
|
-
visible: true,
|
|
16525
|
-
},
|
|
16526
|
-
/* Display Options */
|
|
16527
|
-
// {
|
|
16528
|
-
// name: 'showCurrentCondition',
|
|
16529
|
-
// title: 'Show Current Condition',
|
|
16530
|
-
// group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
16531
|
-
// schema: {
|
|
16532
|
-
// defaultValue: true,
|
|
16533
|
-
// dataType: 'boolean',
|
|
16534
|
-
// interface: {
|
|
16535
|
-
// name: 'showCurrentCondition',
|
|
16536
|
-
// path: 'options.showCurrentCondition',
|
|
16537
|
-
// type: AXPWidgetsCatalog.toggle,
|
|
16538
|
-
// },
|
|
16539
|
-
// },
|
|
16540
|
-
// visible: true,
|
|
16541
|
-
// },
|
|
16542
|
-
// {
|
|
16543
|
-
// name: 'showTemperature',
|
|
16544
|
-
// title: 'Show Temperature',
|
|
16545
|
-
// group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
16546
|
-
// schema: {
|
|
16547
|
-
// defaultValue: true,
|
|
16548
|
-
// dataType: 'boolean',
|
|
16549
|
-
// interface: {
|
|
16550
|
-
// name: 'showTemperature',
|
|
16551
|
-
// path: 'options.showTemperature',
|
|
16552
|
-
// type: AXPWidgetsCatalog.toggle,
|
|
16553
|
-
// },
|
|
16554
|
-
// },
|
|
16555
|
-
// visible: true,
|
|
16556
|
-
// },
|
|
16557
|
-
// {
|
|
16558
|
-
// name: 'showCurrentCondition',
|
|
16559
|
-
// title: 'Show Current Condition',
|
|
16560
|
-
// group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
16561
|
-
// schema: {
|
|
16562
|
-
// defaultValue: true,
|
|
16563
|
-
// dataType: 'boolean',
|
|
16564
|
-
// interface: {
|
|
16565
|
-
// name: 'showCurrentCondition',
|
|
16566
|
-
// path: 'options.showCurrentCondition',
|
|
16567
|
-
// type: AXPWidgetsCatalog.toggle,
|
|
16568
|
-
// },
|
|
16569
|
-
// },
|
|
16570
|
-
// visible: true,
|
|
16571
|
-
// },
|
|
16572
|
-
// {
|
|
16573
|
-
// name: 'showTemperature',
|
|
16574
|
-
// title: 'Show Temperature',
|
|
16575
|
-
// group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
16576
|
-
// schema: {
|
|
16577
|
-
// defaultValue: true,
|
|
16578
|
-
// dataType: 'boolean',
|
|
16579
|
-
// interface: {
|
|
16580
|
-
// name: 'showTemperature',
|
|
16581
|
-
// path: 'options.showTemperature',
|
|
16582
|
-
// type: AXPWidgetsCatalog.toggle,
|
|
16583
|
-
// },
|
|
16584
|
-
// },
|
|
16585
|
-
// visible: true,
|
|
16586
|
-
// },
|
|
16587
|
-
{
|
|
16588
|
-
name: 'showHumidity',
|
|
16589
|
-
title: 'Show Humidity',
|
|
16590
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
16591
|
-
schema: {
|
|
16592
|
-
defaultValue: true,
|
|
16593
|
-
dataType: 'boolean',
|
|
16594
|
-
interface: {
|
|
16595
|
-
name: 'showHumidity',
|
|
16596
|
-
path: 'options.showHumidity',
|
|
16597
|
-
type: AXPWidgetsCatalog.toggle,
|
|
16598
|
-
},
|
|
16599
|
-
},
|
|
16600
|
-
visible: true,
|
|
16601
|
-
},
|
|
16602
|
-
{
|
|
16603
|
-
name: 'showWind',
|
|
16604
|
-
title: 'Show Wind Speed',
|
|
16605
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
16606
|
-
schema: {
|
|
16607
|
-
defaultValue: true,
|
|
16608
|
-
dataType: 'boolean',
|
|
16609
|
-
interface: {
|
|
16610
|
-
name: 'showWind',
|
|
16611
|
-
path: 'options.showWind',
|
|
16612
|
-
type: AXPWidgetsCatalog.toggle,
|
|
16613
|
-
},
|
|
16614
|
-
},
|
|
16615
|
-
visible: true,
|
|
16616
|
-
},
|
|
16617
|
-
{
|
|
16618
|
-
name: 'showForecast',
|
|
16619
|
-
title: 'Show Forecast',
|
|
16620
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
16621
|
-
schema: {
|
|
16622
|
-
defaultValue: true,
|
|
16623
|
-
dataType: 'boolean',
|
|
16624
|
-
interface: {
|
|
16625
|
-
name: 'showForecast',
|
|
16626
|
-
path: 'options.showForecast',
|
|
16627
|
-
type: AXPWidgetsCatalog.toggle,
|
|
16628
|
-
},
|
|
16629
|
-
},
|
|
16630
|
-
visible: true,
|
|
16631
|
-
},
|
|
16632
|
-
{
|
|
16633
|
-
name: 'forecastDays',
|
|
16634
|
-
title: 'Forecast Days',
|
|
16635
|
-
group: AXP_APPEARANCE_PROPERTY_GROUP,
|
|
16636
|
-
schema: {
|
|
16637
|
-
defaultValue: 5,
|
|
16638
|
-
dataType: 'number',
|
|
16639
|
-
interface: {
|
|
16640
|
-
name: 'forecastDays',
|
|
16641
|
-
path: 'options.forecastDays',
|
|
16642
|
-
type: AXPWidgetsCatalog.number,
|
|
16643
|
-
options: {
|
|
16644
|
-
minValue: 1,
|
|
16645
|
-
maxValue: 7,
|
|
16646
|
-
step: 1,
|
|
16647
|
-
},
|
|
16648
|
-
},
|
|
16649
|
-
},
|
|
16650
|
-
visible: true,
|
|
16651
|
-
},
|
|
16652
|
-
/* Units Settings */
|
|
16653
|
-
{
|
|
16654
|
-
name: 'temperatureUnit',
|
|
16655
|
-
title: 'Temperature Unit',
|
|
16656
|
-
group: AXP_DATA_PROPERTY_GROUP,
|
|
16657
|
-
schema: {
|
|
16658
|
-
defaultValue: '°C',
|
|
16659
|
-
dataType: 'string',
|
|
16660
|
-
interface: {
|
|
16661
|
-
name: 'temperatureUnit',
|
|
16662
|
-
path: 'options.temperatureUnit',
|
|
16663
|
-
type: AXPWidgetsCatalog.select,
|
|
16664
|
-
options: {
|
|
16665
|
-
dataSource: ['°C', '°F'],
|
|
16666
|
-
},
|
|
16667
|
-
},
|
|
16668
|
-
},
|
|
16669
|
-
visible: true,
|
|
16670
|
-
},
|
|
16671
|
-
{
|
|
16672
|
-
name: 'windSpeedUnit',
|
|
16673
|
-
title: 'Wind Speed Unit',
|
|
16674
|
-
group: AXP_DATA_PROPERTY_GROUP,
|
|
16675
|
-
schema: {
|
|
16676
|
-
defaultValue: 'km/h',
|
|
16677
|
-
dataType: 'string',
|
|
16678
|
-
interface: {
|
|
16679
|
-
name: 'windSpeedUnit',
|
|
16680
|
-
path: 'options.windSpeedUnit',
|
|
16681
|
-
type: AXPWidgetsCatalog.select,
|
|
16682
|
-
options: {
|
|
16683
|
-
dataSource: ['km/h', 'mph', 'm/s'],
|
|
16684
|
-
},
|
|
16685
|
-
},
|
|
16686
|
-
},
|
|
16687
|
-
visible: true,
|
|
16688
|
-
},
|
|
16689
|
-
/* Refresh Settings */
|
|
16690
|
-
{
|
|
16691
|
-
name: 'autoRefresh',
|
|
16692
|
-
title: 'Auto Refresh',
|
|
16693
|
-
group: AXP_BEHAVIOR_PROPERTY_GROUP,
|
|
16694
|
-
schema: {
|
|
16695
|
-
defaultValue: true,
|
|
16696
|
-
dataType: 'boolean',
|
|
16697
|
-
interface: {
|
|
16698
|
-
name: 'autoRefresh',
|
|
16699
|
-
path: 'options.autoRefresh',
|
|
16700
|
-
type: AXPWidgetsCatalog.toggle,
|
|
16701
|
-
},
|
|
16702
|
-
},
|
|
16703
|
-
visible: true,
|
|
16704
|
-
},
|
|
16705
|
-
{
|
|
16706
|
-
name: 'refreshInterval',
|
|
16707
|
-
title: 'Refresh Interval (minutes)',
|
|
16708
|
-
group: AXP_BEHAVIOR_PROPERTY_GROUP,
|
|
16709
|
-
schema: {
|
|
16710
|
-
defaultValue: 15,
|
|
16711
|
-
dataType: 'number',
|
|
16712
|
-
interface: {
|
|
16713
|
-
name: 'refreshInterval',
|
|
16714
|
-
path: 'options.refreshInterval',
|
|
16715
|
-
type: AXPWidgetsCatalog.select,
|
|
16716
|
-
options: {
|
|
16717
|
-
dataSource: [5, 10, 15, 30, 60],
|
|
16718
|
-
},
|
|
16719
|
-
},
|
|
16720
|
-
},
|
|
16721
|
-
visible: true,
|
|
16722
|
-
},
|
|
16723
|
-
],
|
|
16724
|
-
components: {
|
|
16725
|
-
view: {
|
|
16726
|
-
component: () => Promise.resolve().then(function () { return weatherWidget_component; }).then((c) => c.AXPWeatherWidgetViewComponent),
|
|
16727
|
-
},
|
|
16728
|
-
},
|
|
16729
|
-
meta: {
|
|
16730
|
-
dimensions: {
|
|
16731
|
-
width: 3,
|
|
16732
|
-
height: 7,
|
|
16733
|
-
minWidth: 2,
|
|
16734
|
-
minHeight: 4,
|
|
16735
|
-
maxWidth: 4,
|
|
16736
|
-
maxHeight: 8,
|
|
16737
|
-
},
|
|
16738
|
-
},
|
|
16739
|
-
};
|
|
16740
|
-
|
|
16741
|
-
// export interface AXPWidgetOptions {
|
|
16742
|
-
// }
|
|
16743
|
-
// export interface AXPWidgetConfig<T extends AXPWidgetOptions> {
|
|
16744
|
-
// name: string;
|
|
16745
|
-
// options: T;
|
|
16746
|
-
// }
|
|
16747
|
-
// export interface AXpTextBoxWidget {
|
|
16748
|
-
// }
|
|
16749
|
-
// export interface AXPTextBoxWidgetOptions {
|
|
16750
|
-
// disabled?: boolean;
|
|
16751
|
-
// readonly?: boolean;
|
|
16752
|
-
// hasClearButton?: boolean;
|
|
16753
|
-
// placeholder?: string;
|
|
16754
|
-
// }
|
|
16755
|
-
|
|
16756
12389
|
class AXPCronJobWidgetViewComponent extends AXPValueWidgetComponent {
|
|
16757
12390
|
constructor() {
|
|
16758
12391
|
super(...arguments);
|
|
@@ -17819,7 +13452,7 @@ class AXPTabularDataWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
17819
13452
|
this.platform = inject(AXPlatform);
|
|
17820
13453
|
}
|
|
17821
13454
|
async openPopup() {
|
|
17822
|
-
const { AXPTabularDataPopupComponent } = await import('./acorex-platform-widgets-tabular-data-edit-popup.component-
|
|
13455
|
+
const { AXPTabularDataPopupComponent } = await import('./acorex-platform-widgets-tabular-data-edit-popup.component-1IseEVXQ.mjs');
|
|
17823
13456
|
const popupData = await this.popupService.open(AXPTabularDataPopupComponent, {
|
|
17824
13457
|
size: this.platform.is('Mobile') || this.platform.is('SM') ? 'full' : this.columns().length > 3 ? 'lg' : 'md',
|
|
17825
13458
|
header: true,
|
|
@@ -19974,20 +15607,10 @@ class AXPWidgetsModule {
|
|
|
19974
15607
|
AXPBetweenExpressionValidationWidget,
|
|
19975
15608
|
AXPEqualValidationWidget,
|
|
19976
15609
|
AXPCallbackValidationWidget,
|
|
19977
|
-
//charts
|
|
19978
|
-
AXPDonutChartWidget,
|
|
19979
|
-
AXPBarChartWidget,
|
|
19980
|
-
AXPLineChartWidget,
|
|
19981
|
-
AXPGaugeChartWidget,
|
|
19982
|
-
AXPStickyNoteWidget,
|
|
19983
|
-
AXPClockCalendarWidget,
|
|
19984
|
-
AXPWeatherWidget,
|
|
19985
15610
|
AXPMetaDataWidget,
|
|
19986
15611
|
//Custom Widgets
|
|
19987
15612
|
AXPNumberUnitBoxWidget,
|
|
19988
15613
|
AXPPanelWidget,
|
|
19989
|
-
AXPNotificationWidget,
|
|
19990
|
-
AXPTaskListWidget,
|
|
19991
15614
|
],
|
|
19992
15615
|
})] }); }
|
|
19993
15616
|
}
|
|
@@ -20055,20 +15678,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImpor
|
|
|
20055
15678
|
AXPBetweenExpressionValidationWidget,
|
|
20056
15679
|
AXPEqualValidationWidget,
|
|
20057
15680
|
AXPCallbackValidationWidget,
|
|
20058
|
-
//charts
|
|
20059
|
-
AXPDonutChartWidget,
|
|
20060
|
-
AXPBarChartWidget,
|
|
20061
|
-
AXPLineChartWidget,
|
|
20062
|
-
AXPGaugeChartWidget,
|
|
20063
|
-
AXPStickyNoteWidget,
|
|
20064
|
-
AXPClockCalendarWidget,
|
|
20065
|
-
AXPWeatherWidget,
|
|
20066
15681
|
AXPMetaDataWidget,
|
|
20067
15682
|
//Custom Widgets
|
|
20068
15683
|
AXPNumberUnitBoxWidget,
|
|
20069
15684
|
AXPPanelWidget,
|
|
20070
|
-
AXPNotificationWidget,
|
|
20071
|
-
AXPTaskListWidget,
|
|
20072
15685
|
],
|
|
20073
15686
|
}),
|
|
20074
15687
|
],
|
|
@@ -20085,5 +15698,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImpor
|
|
|
20085
15698
|
* Generated bundle index. Do not edit.
|
|
20086
15699
|
*/
|
|
20087
15700
|
|
|
20088
|
-
export { AXPAdvancedGridItemWidget, AXPAdvancedGridItemWidgetDesignerComponent, AXPAdvancedGridItemWidgetPrintComponent, AXPAdvancedGridItemWidgetViewComponent, AXPAdvancedGridOptionsWidget, AXPAdvancedGridOptionsWidgetEditComponent, AXPAdvancedGridWidget, AXPAdvancedGridWidgetDesignerComponent, AXPAdvancedGridWidgetViewComponent, AXPAvatarWidget, AXPAvatarWidgetColumnComponent, AXPAvatarWidgetDesignerComponent, AXPAvatarWidgetEditComponent, AXPAvatarWidgetPrintComponent, AXPAvatarWidgetViewComponent,
|
|
15701
|
+
export { AXPAdvancedGridItemWidget, AXPAdvancedGridItemWidgetDesignerComponent, AXPAdvancedGridItemWidgetPrintComponent, AXPAdvancedGridItemWidgetViewComponent, AXPAdvancedGridOptionsWidget, AXPAdvancedGridOptionsWidgetEditComponent, AXPAdvancedGridWidget, AXPAdvancedGridWidgetDesignerComponent, AXPAdvancedGridWidgetViewComponent, AXPAvatarWidget, AXPAvatarWidgetColumnComponent, AXPAvatarWidgetDesignerComponent, AXPAvatarWidgetEditComponent, AXPAvatarWidgetPrintComponent, AXPAvatarWidgetViewComponent, AXPBetweenExpressionValidationWidget, AXPBetweenValidationWidgetEditComponent, AXPBlockWidget, AXPBlockWidgetDesignerComponent, AXPBlockWidgetViewComponent, AXPBorderWidget, AXPBorderWidgetEditComponent, AXPButtonWidget, AXPButtonWidgetViewComponent, AXPCallbackValidationWidget, AXPCallbackValidationWidgetEditComponent, AXPCheckBoxWidget, AXPCheckBoxWidgetEditComponent, AXPContactWidget, AXPContactWidgetColumnComponent, AXPContactWidgetEditComponent, AXPContactWidgetPrintComponent, AXPContactWidgetViewComponent, AXPDateTimeBoxWidget, AXPDateTimeBoxWidgetColumnComponent, AXPDateTimeBoxWidgetEditComponent, AXPDateTimeBoxWidgetFilterComponent, AXPDateTimeBoxWidgetPrintComponent, AXPDateTimeBoxWidgetViewComponent, AXPEmailBoxWidget, AXPEmailBoxWidgetColumnComponent, AXPEmailBoxWidgetEditComponent, AXPEmailBoxWidgetFilterComponent, AXPEmailBoxWidgetPrintComponent, AXPEmailBoxWidgetViewComponent, AXPEqualValidationWidget, AXPEqualValidationWidgetEditComponent, AXPFileBoxWidget, AXPFileBoxWidgetColumnComponent, AXPFileBoxWidgetEditComponent, AXPFileBoxWidgetFilterComponent, AXPFileBoxWidgetPrintComponent, AXPFileBoxWidgetViewComponent, AXPFileManagementService, AXPFlexOptionsWidget, AXPFlexOptionsWidgetEditComponent, AXPGalleryWidget, AXPGalleryWidgetEditComponent, AXPGalleryWidgetPrintComponent, AXPGalleryWidgetViewComponent, AXPGreaterThanExpressionValidationWidget, AXPGreaterThanValidationWidgetEditComponent, AXPGridOptionsWidget, AXPGridOptionsWidgetEditComponent, AXPLargeTextWidget, AXPLargeTextWidgetColumnComponent, AXPLargeTextWidgetEditComponent, AXPLargeTextWidgetFilterComponent, AXPLargeTextWidgetPrintComponent, AXPLargeTextWidgetViewComponent, AXPLessThanExpressionValidationWidget, AXPLessThanValidationWidgetEditComponent, AXPLinkWidget, AXPLinkWidgetColumnComponent, AXPLinkWidgetEditComponent, AXPLinkWidgetFilterComponent, AXPLinkWidgetPrintComponent, AXPLinkWidgetViewComponent, AXPMapBoxWidget, AXPMapBoxWidgetEditComponent, AXPMapBoxWidgetViewComponent, AXPMaxLengthExpressionValidationWidget, AXPMaxLengthValidationWidgetEditComponent, AXPMinLengthExpressionValidationWidget, AXPMinLengthValidationWidgetEditComponent, AXPNumberBoxWidget, AXPNumberBoxWidgetColumnComponent, AXPNumberBoxWidgetEditComponent, AXPNumberBoxWidgetFilterComponent, AXPNumberBoxWidgetPrintComponent, AXPNumberBoxWidgetViewComponent, AXPPageWidget, AXPPageWidgetViewComponent, AXPPasswordBoxWidget, AXPPasswordBoxWidgetColumnComponent, AXPPasswordBoxWidgetEditComponent, AXPPasswordBoxWidgetFilterComponent, AXPPasswordBoxWidgetPrintComponent, AXPPasswordBoxWidgetViewComponent, AXPPhoneBoxWidget, AXPPhoneBoxWidgetColumnComponent, AXPPhoneBoxWidgetEditComponent, AXPPhoneBoxWidgetFilterComponent, AXPPhoneBoxWidgetPrintComponent, AXPPhoneBoxWidgetViewComponent, AXPPropertyEditorHelper, AXPRegularExpressionValidationWidget, AXPRegularExpressionValidationWidgetEditComponent, AXPRepeaterWidget, AXPRepeaterWidgetDesignerComponent, AXPRepeaterWidgetEditComponent, AXPRepeaterWidgetPrintComponent, AXPRepeaterWidgetViewComponent, AXPRequiredValidationWidget, AXPRequiredValidationWidgetEditComponent, AXPRichTextWidget, AXPRichTextWidgetColumnComponent, AXPRichTextWidgetEditComponent, AXPRichTextWidgetFilterComponent, AXPRichTextWidgetPrintComponent, AXPRichTextWidgetViewComponent, AXPSelectBoxWidget, AXPSelectBoxWidgetColumnComponent, AXPSelectBoxWidgetEditComponent, AXPSelectBoxWidgetFilterComponent, AXPSelectBoxWidgetPrintComponent, AXPSelectBoxWidgetViewComponent, AXPSelectionListWidget, AXPSelectionListWidgetColumnComponent, AXPSelectionListWidgetDesignerComponent, AXPSelectionListWidgetEditComponent, AXPSelectionListWidgetFilterComponent, AXPSelectionListWidgetPrintComponent, AXPSelectionListWidgetViewComponent, AXPSignatureWidget, AXPSignatureWidgetColumnComponent, AXPSignatureWidgetEditComponent, AXPSignatureWidgetFilterComponent, AXPSignatureWidgetPrintComponent, AXPSignatureWidgetViewComponent, AXPSingleFileBoxWidget, AXPSingleFileBoxWidgetColumnComponent, AXPSingleFileBoxWidgetEditComponent, AXPSingleFileBoxWidgetFilterComponent, AXPSingleFileBoxWidgetPrintComponent, AXPSingleFileBoxWidgetViewComponent, AXPSpacingWidget, AXPSpacingWidgetEditComponent, AXPTemplateBoxWidget, AXPTemplateBoxWidgetColumnComponent, AXPTemplateBoxWidgetEditComponent, AXPTemplateBoxWidgetFilterComponent, AXPTemplateBoxWidgetPrintComponent, AXPTemplateBoxWidgetViewComponent, AXPTextBoxWidget, AXPTextBoxWidgetColumnComponent, AXPTextBoxWidgetEditComponent, AXPTextBoxWidgetFilterComponent, AXPTextBoxWidgetPrintComponent$1 as AXPTextBoxWidgetPrintComponent, AXPTextBoxWidgetViewComponent, AXPToggleWidget, AXPToggleWidgetColumnComponent, AXPToggleWidgetEditComponent, AXPToggleWidgetFilterComponent, AXPToggleWidgetPrintComponent, AXPToggleWidgetViewComponent, AXPWidgetsModule, AXP_ALLOW_MULTIPLE_PROPERTY, AXP_ALLOW_SEARCH_PROPERTY, AXP_ANIMATION_PROPERTY_GROUP, AXP_APPEARANCE_PROPERTY_GROUP, AXP_BEHAVIOR_PROPERTY_GROUP, AXP_BETWEEN_VALIDATION_PROPERTY, AXP_BG_COLOR_PROPERTY, AXP_BOX_MODEL_PROPERTY_GROUP, AXP_CALLBACK_VALIDATION_PROPERTY, AXP_COLOR_PROPERTY, AXP_CONTENT_PROPERTY, AXP_DATA_PATH_PROPERTY, AXP_DATA_PROPERTY_GROUP, AXP_DATA_SOURCE_PROPERTIES, AXP_DATA_SOURCE_PROPERTY, AXP_DATA_SOURCE_TEXT_FIELD, AXP_DATA_SOURCE_VALUE_FIELD, AXP_DATE_FORMAT_PROPERTY, AXP_DESCRIPTION_PROPERTY, AXP_DIRECTION_PROPERTY, AXP_DISABLED_PROPERTY, AXP_DOWNLOADABLE_PROPERTY, AXP_EQUAL_VALIDATION_PROPERTY, AXP_FALSY_TEXT_PROPERTY, AXP_FONT_SIZE_PROPERTY, AXP_Flex_Box_Align_Options, AXP_Flex_Box_Alignments, AXP_Flex_Box_Justify_Options, AXP_GREATER_THAN_VALIDATION_PROPERTY, AXP_Grid_Box_Align_Items_Options, AXP_Grid_Box_Alignments, AXP_Grid_Box_Justify_Items_Options, AXP_HAS_CLEAR_BUTTON_PROPERTY, AXP_HAS_COPY_ICON_PROPERTY, AXP_HAS_EYE_ICON_PROPERTY, AXP_HAS_ICON_PROPERTY, AXP_HAS_LABEL_PROPERTY, AXP_ICON_PROPERTY, AXP_IS_LOADING_PROPERTY, AXP_LABEL_PROPERTY, AXP_LAYOUT_ADVANCED_GRID_PROPERTY, AXP_LAYOUT_BORDER_PROPERTY, AXP_LAYOUT_COLUMNS_PROPERTY, AXP_LAYOUT_COL_END_PROPERTY, AXP_LAYOUT_COL_SPAN_PROPERTY, AXP_LAYOUT_COL_START_PROPERTY, AXP_LAYOUT_FLEX_PROPERTY, AXP_LAYOUT_FLEX_PROPERTY_GROUP, AXP_LAYOUT_GAP_PROPERTY, AXP_LAYOUT_GRID_ITEM_PROPERTIES, AXP_LAYOUT_GRID_PROPERTIES, AXP_LAYOUT_GRID_PROPERTY, AXP_LAYOUT_GRID_PROPERTY_GROUP, AXP_LAYOUT_GRID_ROW_PROPERTIES, AXP_LAYOUT_ROWS_PROPERTY, AXP_LAYOUT_SPACING_PROPERTY, AXP_LESS_THAN_VALIDATION_PROPERTY, AXP_MAX_LENGTH_VALIDATION_PROPERTY, AXP_MIN_LENGTH_VALIDATION_PROPERTY, AXP_NAME_PROPERTY, AXP_PLACEHOLDER_PROPERTY, AXP_READONLY_PROPERTY, AXP_REGULAR_EXPRESSION_VALIDATION_PROPERTY, AXP_REQUIRED_VALIDATION_PROPERTY, AXP_STYLE_COLOR_PROPERTY, AXP_STYLE_LOOK_PROPERTY, AXP_STYLING_PROPERTY_GROUP, AXP_TABLE_COLUMN_HEIGHT_PROPERTY, AXP_TABLE_COLUMN_WIDTH_PROPERTY, AXP_TEXT_FIELD_PROPERTY, AXP_TEXT_PROPERTY, AXP_THEME_PROPERTY, AXP_TITLE_PROPERTY, AXP_TRULY_TEXT_PROPERTY, AXP_VALIDATION_PROPERTY_GROUP, AXP_VALUE_FIELD_PROPERTY, AXP_WIDGET_PROPERTY_GROUP, AXP_default_Border_Box_Units, AXP_default_Border_Box_Value, AXP_default_Spacing_Box_Units, AXP_default_Spacing_Box_Value, DEFAULT_STRATEGY_CONFIG, STRATEGY_CONFIG_TOKEN, booleanDefaultProperty, findNonEmptyBreakpoints, largeTextDefaultProperty, numberDefaultProperty, numberMaxValueProperty, numberMinValueProperty, plainTextDefaultProperty };
|
|
20089
15702
|
//# sourceMappingURL=acorex-platform-widgets.mjs.map
|