@c8y/tutorial 1023.64.1 → 1023.65.0
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/package.json +7 -7
- package/src/app/app.config.ts +5 -1
- package/src/global-context-widget/global-context-dashboard.component.ts +62 -0
- package/src/global-context-widget/global-context-widget-config.component.ts +35 -0
- package/src/global-context-widget/global-context-widget-view.component.ts +331 -0
- package/src/global-context-widget/index.ts +87 -0
- package/src/global-context-widget/widget-config.model.ts +57 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c8y/tutorial",
|
|
3
|
-
"version": "1023.
|
|
3
|
+
"version": "1023.65.0",
|
|
4
4
|
"description": "This package is used to scaffold a tutorial for Cumulocity IoT Web SDK which explains a lot of concepts.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@c8y/style": "1023.
|
|
7
|
-
"@c8y/ngx-components": "1023.
|
|
8
|
-
"@c8y/client": "1023.
|
|
9
|
-
"@c8y/bootstrap": "1023.
|
|
6
|
+
"@c8y/style": "1023.65.0",
|
|
7
|
+
"@c8y/ngx-components": "1023.65.0",
|
|
8
|
+
"@c8y/client": "1023.65.0",
|
|
9
|
+
"@c8y/bootstrap": "1023.65.0",
|
|
10
10
|
"@angular/cdk": "^20.2.14",
|
|
11
11
|
"monaco-editor": "~0.53.0",
|
|
12
12
|
"ngx-bootstrap": "20.0.2",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"rxjs": "7.8.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@c8y/options": "1023.
|
|
18
|
-
"@c8y/devkit": "1023.
|
|
17
|
+
"@c8y/options": "1023.65.0",
|
|
18
|
+
"@c8y/devkit": "1023.65.0"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@angular/common": ">=20 <21"
|
package/src/app/app.config.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { provideWidgetsResolverSample } from '../widget-resolvers';
|
|
|
15
15
|
import { provideMapExampleNavigator } from '../maps';
|
|
16
16
|
import { provideTranslationsNavigator } from '../translations';
|
|
17
17
|
import { provideLazyWidget } from '../lazy-widget';
|
|
18
|
+
import { provideGlobalContextWidget } from '../global-context-widget';
|
|
18
19
|
import { provideBreadcrumbsNavigator } from '../breadcrumbs';
|
|
19
20
|
import { provideSplitViewSamples } from '../split-view';
|
|
20
21
|
import { provideIconPanelExample } from '../icon-panel';
|
|
@@ -22,6 +23,7 @@ import { provideClientInterceptorSample } from '../client-interceptor';
|
|
|
22
23
|
import { provideUserMenuSample } from '../user-menu';
|
|
23
24
|
import { AlarmsModule } from '@c8y/ngx-components/alarms';
|
|
24
25
|
import { BulkOperationSchedulerModule } from '@c8y/ngx-components/operations/bulk-operation-scheduler';
|
|
26
|
+
import { GlobalContextModule } from '@c8y/ngx-components/global-context';
|
|
25
27
|
import { provideRedirectToLastRoute } from '../redirect-to-last-route';
|
|
26
28
|
import { provideAPIMock } from '../__mocks';
|
|
27
29
|
import { configureWidgetProviders } from '@c8y/ngx-components/widgets/widget-providers';
|
|
@@ -51,12 +53,14 @@ export const appConfig: ApplicationConfig = {
|
|
|
51
53
|
// Get rid of a default version factory
|
|
52
54
|
importProvidersFrom(VersionModule.config({ disableWebSDKPluginVersionFactory: true })),
|
|
53
55
|
...provideLazyWidget(),
|
|
56
|
+
...provideGlobalContextWidget(),
|
|
54
57
|
...configureWidgetProviders(),
|
|
55
58
|
...provideRedirectToLastRoute(),
|
|
56
59
|
...provideAPIMock(),
|
|
57
60
|
...provideBreadcrumbsNavigator(),
|
|
58
61
|
...provideSplitViewSamples(),
|
|
59
62
|
...provideIconPanelExample(),
|
|
60
|
-
importProvidersFrom(AlarmsModule.config({ hybrid: false }))
|
|
63
|
+
importProvidersFrom(AlarmsModule.config({ hybrid: false })),
|
|
64
|
+
importProvidersFrom(GlobalContextModule)
|
|
61
65
|
]
|
|
62
66
|
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { CoreModule, Widget } from '@c8y/ngx-components';
|
|
4
|
+
import { ContextDashboardModule } from '@c8y/ngx-components/context-dashboard';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dashboard page showcasing the Global Time Context v2 widget example.
|
|
8
|
+
*
|
|
9
|
+
* This dashboard demonstrates:
|
|
10
|
+
* - GlobalContextConnectorComponent for dashboard mode (linked to global context)
|
|
11
|
+
* - LocalControlsComponent for config/view_and_config modes (local controls)
|
|
12
|
+
* - Time context integration with global dashboard controls in the action bar
|
|
13
|
+
*
|
|
14
|
+
* The widget responds to:
|
|
15
|
+
* - Time range changes (Live mode: last hour, etc. / History mode: date range)
|
|
16
|
+
* - Auto-refresh toggle
|
|
17
|
+
* - Manual refresh
|
|
18
|
+
* - Aggregation settings (in History mode)
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'c8y-global-context-dashboard',
|
|
22
|
+
standalone: true,
|
|
23
|
+
imports: [CommonModule, CoreModule, ContextDashboardModule],
|
|
24
|
+
template: `
|
|
25
|
+
<c8y-title>Global Time Context Example</c8y-title>
|
|
26
|
+
<c8y-context-dashboard
|
|
27
|
+
name="global-context-example"
|
|
28
|
+
[defaultWidgets]="defaultWidgets"
|
|
29
|
+
[canDelete]="false"
|
|
30
|
+
></c8y-context-dashboard>
|
|
31
|
+
`
|
|
32
|
+
})
|
|
33
|
+
export class GlobalContextDashboardComponent {
|
|
34
|
+
defaultWidgets: Widget[] = [
|
|
35
|
+
{
|
|
36
|
+
_x: 0,
|
|
37
|
+
_y: 0,
|
|
38
|
+
_width: 6,
|
|
39
|
+
_height: 5,
|
|
40
|
+
componentId: 'tutorial.global-context-widget',
|
|
41
|
+
config: {
|
|
42
|
+
// IMPORTANT: displayMode must be 'dashboard' for GlobalContextConnector to be used.
|
|
43
|
+
// This tells the system that this widget is linked to the global time context.
|
|
44
|
+
displayMode: 'dashboard'
|
|
45
|
+
},
|
|
46
|
+
title: 'Widget 1 - Linked to Global Context',
|
|
47
|
+
id: 'global-context-widget-1'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
_x: 6,
|
|
51
|
+
_y: 0,
|
|
52
|
+
_width: 6,
|
|
53
|
+
_height: 5,
|
|
54
|
+
componentId: 'tutorial.global-context-widget',
|
|
55
|
+
config: {
|
|
56
|
+
displayMode: 'dashboard'
|
|
57
|
+
},
|
|
58
|
+
title: 'Widget 2 - Also Linked',
|
|
59
|
+
id: 'global-context-widget-2'
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Component, Input, OnInit } from '@angular/core';
|
|
2
|
+
import { WidgetConfig } from './widget-config.model';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Widget configuration component.
|
|
6
|
+
*
|
|
7
|
+
* Note: The "Time context" section (display mode, time range, aggregation, etc.)
|
|
8
|
+
* is automatically added by hookWidgetConfig with GlobalContextSectionComponent.
|
|
9
|
+
* This component only handles widget-specific configuration.
|
|
10
|
+
*/
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'c8y-global-context-widget-config',
|
|
13
|
+
standalone: true,
|
|
14
|
+
template: `
|
|
15
|
+
<div class="alert alert-info m-t-16">
|
|
16
|
+
<p class="m-b-0">
|
|
17
|
+
<strong>Note:</strong> The "Time context" section above is automatically injected via
|
|
18
|
+
<code>hookWidgetConfig</code> with <code>GlobalContextSectionComponent</code>. It handles:
|
|
19
|
+
</p>
|
|
20
|
+
<ul class="m-b-0 m-t-8">
|
|
21
|
+
<li>Display mode selection (Dashboard / Configuration / Widget view)</li>
|
|
22
|
+
<li>Time range configuration (Live / History)</li>
|
|
23
|
+
<li>Auto-refresh settings</li>
|
|
24
|
+
<li>Aggregation options</li>
|
|
25
|
+
</ul>
|
|
26
|
+
</div>
|
|
27
|
+
`
|
|
28
|
+
})
|
|
29
|
+
export class GlobalContextWidgetConfigComponent implements OnInit {
|
|
30
|
+
@Input() config: WidgetConfig = {};
|
|
31
|
+
|
|
32
|
+
ngOnInit() {
|
|
33
|
+
console.log('Widget config loaded:', this.config);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { Component, Input, OnInit, inject, signal, computed } from '@angular/core';
|
|
2
|
+
import { DashboardChildComponent, DatePipe } from '@c8y/ngx-components';
|
|
3
|
+
import {
|
|
4
|
+
LocalControlsComponent,
|
|
5
|
+
GlobalContextConnectorComponent,
|
|
6
|
+
GlobalContextState,
|
|
7
|
+
DisplayMode,
|
|
8
|
+
GLOBAL_CONTEXT_DISPLAY_MODE,
|
|
9
|
+
REFRESH_OPTION
|
|
10
|
+
} from '@c8y/ngx-components/global-context';
|
|
11
|
+
import { WidgetConfig, WIDGET_CONTROLS } from './widget-config.model';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Example widget view component demonstrating Global Time Context v2 integration.
|
|
15
|
+
*
|
|
16
|
+
* The `displayMode` determines **where time controls come from** (not visual appearance):
|
|
17
|
+
*
|
|
18
|
+
* **Dashboard mode** (`displayMode: 'dashboard'`):
|
|
19
|
+
* - Time is controlled by the dashboard-level action bar (shared across all widgets)
|
|
20
|
+
* - Uses `GlobalContextConnectorComponent` to connect the widget to the global time context
|
|
21
|
+
* - All widgets on the dashboard share the same time range
|
|
22
|
+
*
|
|
23
|
+
* **Config mode** (`displayMode: 'config'`):
|
|
24
|
+
* - Time is controlled only in the widget configuration panel
|
|
25
|
+
* - Uses `LocalControlsComponent` with the widget's stored time range
|
|
26
|
+
* - Widget operates with a fixed, pre-configured time range at runtime
|
|
27
|
+
*
|
|
28
|
+
* **View_and_config mode** (`displayMode: 'view_and_config'`):
|
|
29
|
+
* - Time is controlled by inline controls rendered inside the widget itself
|
|
30
|
+
* - Uses `LocalControlsComponent` for independent, user-adjustable time controls
|
|
31
|
+
* - Widget has its own time range, not linked to other widgets
|
|
32
|
+
*
|
|
33
|
+
* Key integration points:
|
|
34
|
+
* - `[controls]` - Defines which features are available (live time, history, aggregation, etc.)
|
|
35
|
+
* - `[config]` - Current time context state (dateFrom, dateTo, aggregation, etc.)
|
|
36
|
+
* - `[isLoading]` - When true, pauses auto-refresh counter until loading completes
|
|
37
|
+
* - `(configChange)` - Emitted when user changes time context settings
|
|
38
|
+
* - `(refresh)` - Emitted on auto-refresh tick or manual refresh
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```html
|
|
42
|
+
* <!-- In dashboard mode, use GlobalContextConnector -->
|
|
43
|
+
* <c8y-global-context-connector
|
|
44
|
+
* [controls]="widgetControls"
|
|
45
|
+
* [config]="contextConfig()"
|
|
46
|
+
* [isLoading]="isLoading()"
|
|
47
|
+
* [dashboardChild]="getDashboardChild()"
|
|
48
|
+
* (configChange)="onContextChange($event)"
|
|
49
|
+
* (refresh)="onRefresh()"
|
|
50
|
+
* ></c8y-global-context-connector>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
@Component({
|
|
54
|
+
selector: 'c8y-global-context-widget-view',
|
|
55
|
+
standalone: true,
|
|
56
|
+
imports: [DatePipe, LocalControlsComponent, GlobalContextConnectorComponent],
|
|
57
|
+
template: `
|
|
58
|
+
<!-- Dashboard mode: Connect to global time context -->
|
|
59
|
+
@if (displayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {
|
|
60
|
+
<c8y-global-context-connector
|
|
61
|
+
[controls]="widgetControls"
|
|
62
|
+
[config]="contextConfig()"
|
|
63
|
+
[isLoading]="isLoading()"
|
|
64
|
+
[dashboardChild]="getDashboardChild()"
|
|
65
|
+
[linked]="isLinkedToGlobal()"
|
|
66
|
+
(configChange)="onContextChange($event)"
|
|
67
|
+
(refresh)="onRefresh()"
|
|
68
|
+
></c8y-global-context-connector>
|
|
69
|
+
} @else {
|
|
70
|
+
<!-- Config/View_and_config modes: Local controls -->
|
|
71
|
+
<c8y-local-controls
|
|
72
|
+
[controls]="widgetControls"
|
|
73
|
+
[displayMode]="displayMode()"
|
|
74
|
+
[config]="contextConfig()"
|
|
75
|
+
[isLoading]="isLoading()"
|
|
76
|
+
(configChange)="onContextChange($event)"
|
|
77
|
+
(refresh)="onRefresh()"
|
|
78
|
+
></c8y-local-controls>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
<!-- Widget content -->
|
|
82
|
+
<div class="p-16">
|
|
83
|
+
<!-- Status indicator -->
|
|
84
|
+
<div class="m-b-16">
|
|
85
|
+
<span class="text-muted small">Mode</span>
|
|
86
|
+
<p class="m-b-0">
|
|
87
|
+
<strong>{{ isLiveMode() ? 'Live' : 'History' }}</strong>
|
|
88
|
+
</p>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Time Range -->
|
|
92
|
+
@if (contextConfig().dateTimeContext; as dtc) {
|
|
93
|
+
<div class="row m-b-16">
|
|
94
|
+
<div class="col-xs-6">
|
|
95
|
+
<p class="text-muted small m-b-4">From</p>
|
|
96
|
+
<p class="text-medium m-b-0">{{ dtc.dateFrom | date: 'medium' }}</p>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="col-xs-6">
|
|
99
|
+
<p class="text-muted small m-b-4">To</p>
|
|
100
|
+
<p class="text-medium m-b-0">{{ dtc.dateTo | date: 'medium' }}</p>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Settings row -->
|
|
105
|
+
<div class="d-flex flex-wrap p-t-8 p-b-8 separator-top">
|
|
106
|
+
<div class="m-r-24">
|
|
107
|
+
<span class="text-muted small">Duration</span>
|
|
108
|
+
<p class="m-b-0">
|
|
109
|
+
<strong>{{ formatDuration() }}</strong>
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="m-r-24">
|
|
113
|
+
<span class="text-muted small">Interval</span>
|
|
114
|
+
<p class="m-b-0">
|
|
115
|
+
<code>{{ dtc.interval || 'custom' }}</code>
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
@if (contextConfig().aggregation) {
|
|
119
|
+
<div>
|
|
120
|
+
<span class="text-muted small">Aggregation</span>
|
|
121
|
+
<p class="m-b-0">
|
|
122
|
+
<code>{{ contextConfig().aggregation }}</code>
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
}
|
|
126
|
+
</div>
|
|
127
|
+
} @else {
|
|
128
|
+
<p class="text-muted">Waiting for time context...</p>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
<!-- Data fetch status -->
|
|
132
|
+
<div class="d-flex a-i-center p-t-8 separator-top">
|
|
133
|
+
@if (isLoading()) {
|
|
134
|
+
<i class="fa fa-spinner fa-spin m-r-8"></i>
|
|
135
|
+
<span>Loading...</span>
|
|
136
|
+
} @else {
|
|
137
|
+
<span class="text-muted small m-r-16"
|
|
138
|
+
>Last fetch: {{ lastFetchTime | date: 'mediumTime' }}</span
|
|
139
|
+
>
|
|
140
|
+
<span class="text-muted small">Fetches: {{ fetchCount }}</span>
|
|
141
|
+
}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
`
|
|
145
|
+
})
|
|
146
|
+
export class GlobalContextWidgetViewComponent implements OnInit {
|
|
147
|
+
/** Reference to DashboardChildComponent, required for GlobalContextConnector */
|
|
148
|
+
private dashboardChild = inject(DashboardChildComponent);
|
|
149
|
+
|
|
150
|
+
/** Widget configuration passed from the dashboard */
|
|
151
|
+
@Input() config: WidgetConfig;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Determines where the time controls come from:
|
|
155
|
+
* - 'dashboard': time controlled by the dashboard action bar (shared across widgets)
|
|
156
|
+
* - 'config': time controlled only in the widget configuration panel (fixed at runtime)
|
|
157
|
+
* - 'view_and_config': time controlled by inline controls inside the widget
|
|
158
|
+
*/
|
|
159
|
+
displayMode = signal<DisplayMode>(GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD);
|
|
160
|
+
|
|
161
|
+
/** Current time context state (dateTimeContext, aggregation, refreshOption, etc.) */
|
|
162
|
+
contextConfig = signal<GlobalContextState>({});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Controls link state to global context:
|
|
166
|
+
* - `true` or `undefined`: Widget follows global time context (receives all changes)
|
|
167
|
+
* - `false`: Widget uses local state, ignores global context changes
|
|
168
|
+
*
|
|
169
|
+
* Set to `false` when user is actively interacting with the widget
|
|
170
|
+
* (scrolling, zooming, selecting) to prevent global changes from
|
|
171
|
+
* interrupting their action. Set back to `true` when interaction ends.
|
|
172
|
+
*/
|
|
173
|
+
isLinkedToGlobal = signal<boolean | undefined>(undefined);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Loading state signal. Pass this to the connector/controls.
|
|
177
|
+
* When `true`, the auto-refresh counter pauses until loading completes.
|
|
178
|
+
* This prevents data fetches from stacking up during slow API calls.
|
|
179
|
+
*/
|
|
180
|
+
isLoading = signal<boolean>(false);
|
|
181
|
+
|
|
182
|
+
/** Widget controls - shared with index.ts to keep registration and runtime in sync. */
|
|
183
|
+
readonly widgetControls = WIDGET_CONTROLS;
|
|
184
|
+
|
|
185
|
+
/** Expose display mode constants for use in template */
|
|
186
|
+
readonly GLOBAL_CONTEXT_DISPLAY_MODE = GLOBAL_CONTEXT_DISPLAY_MODE;
|
|
187
|
+
|
|
188
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
189
|
+
// DEMO-ONLY PROPERTIES - You can ignore these, they're just for visualization
|
|
190
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/** @ignore Demo only: Timestamp of last data fetch */
|
|
193
|
+
lastFetchTime: Date | null = null;
|
|
194
|
+
|
|
195
|
+
/** @ignore Demo only: Count of data fetches */
|
|
196
|
+
fetchCount = 0;
|
|
197
|
+
|
|
198
|
+
/** @ignore Demo only: Computed flag for Live mode display */
|
|
199
|
+
isLiveMode = computed(() => this.contextConfig().refreshOption === REFRESH_OPTION.LIVE);
|
|
200
|
+
|
|
201
|
+
/** @ignore Demo only: Computed flag for auto-refresh display */
|
|
202
|
+
isAutoRefreshActive = computed(() => this.contextConfig().isAutoRefreshEnabled === true);
|
|
203
|
+
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Initialize the widget with configuration from the dashboard.
|
|
208
|
+
* In dashboard mode, waits for GlobalContextConnector to emit first state.
|
|
209
|
+
* In other modes, fetches data immediately.
|
|
210
|
+
*/
|
|
211
|
+
ngOnInit(): void {
|
|
212
|
+
const {
|
|
213
|
+
displayMode = GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD,
|
|
214
|
+
dateTimeContext,
|
|
215
|
+
aggregation,
|
|
216
|
+
isAutoRefreshEnabled,
|
|
217
|
+
refreshInterval,
|
|
218
|
+
refreshOption
|
|
219
|
+
} = this.config;
|
|
220
|
+
|
|
221
|
+
this.displayMode.set(displayMode as DisplayMode);
|
|
222
|
+
this.contextConfig.set({
|
|
223
|
+
dateTimeContext,
|
|
224
|
+
aggregation,
|
|
225
|
+
isAutoRefreshEnabled,
|
|
226
|
+
refreshInterval,
|
|
227
|
+
refreshOption
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// In dashboard mode, GlobalContextConnector will emit the initial state
|
|
231
|
+
// In other modes, we need to fetch data ourselves
|
|
232
|
+
if (displayMode !== GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {
|
|
233
|
+
this.fetchData();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Handle context changes from GlobalContextConnector or LocalControls.
|
|
239
|
+
* Called when user changes time range, aggregation, or other settings.
|
|
240
|
+
*
|
|
241
|
+
* @param event - Contains full context state and diff of what changed
|
|
242
|
+
*/
|
|
243
|
+
onContextChange(event: { context: GlobalContextState; diff: GlobalContextState }): void {
|
|
244
|
+
const { diff, context } = event;
|
|
245
|
+
|
|
246
|
+
// Always update context config for UI reactivity
|
|
247
|
+
this.contextConfig.set(event.context);
|
|
248
|
+
|
|
249
|
+
// Optimization: Skip fetch if only auto-refresh was disabled in Live mode.
|
|
250
|
+
// In Live mode, the time window slides automatically, so we don't need to
|
|
251
|
+
// re-fetch just because auto-refresh was turned off.
|
|
252
|
+
if (
|
|
253
|
+
diff.isAutoRefreshEnabled === false &&
|
|
254
|
+
Object.keys(diff).length === 1 &&
|
|
255
|
+
context.refreshOption === REFRESH_OPTION.LIVE
|
|
256
|
+
) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this.fetchData();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Handle refresh events (manual refresh button or auto-refresh tick).
|
|
265
|
+
*/
|
|
266
|
+
onRefresh(): void {
|
|
267
|
+
this.fetchData();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Required for GlobalContextConnector - provides reference to DashboardChildComponent.
|
|
272
|
+
* This allows the connector to register the widget with the dashboard's context system.
|
|
273
|
+
*/
|
|
274
|
+
getDashboardChild(): DashboardChildComponent {
|
|
275
|
+
return this.dashboardChild;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
279
|
+
// DEMO-ONLY METHOD - You can ignore this, it's just for visualization
|
|
280
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @ignore Demo only: Format the time range duration for display (e.g., "1h 30m", "2d 4h").
|
|
284
|
+
* This is not part of the Global Context integration - just a helper for this demo widget.
|
|
285
|
+
*/
|
|
286
|
+
formatDuration(): string {
|
|
287
|
+
const dtc = this.contextConfig().dateTimeContext;
|
|
288
|
+
if (!dtc?.dateFrom || !dtc?.dateTo) {
|
|
289
|
+
return '-';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const diffMs = new Date(dtc.dateTo).getTime() - new Date(dtc.dateFrom).getTime();
|
|
293
|
+
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
294
|
+
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
295
|
+
|
|
296
|
+
if (hours >= 24) {
|
|
297
|
+
const days = Math.floor(hours / 24);
|
|
298
|
+
return `${days}d ${hours % 24}h`;
|
|
299
|
+
}
|
|
300
|
+
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Fetch data based on current time context.
|
|
307
|
+
* Demonstrates proper loading state management:
|
|
308
|
+
* 1. Set isLoading to true (pauses auto-refresh counter)
|
|
309
|
+
* 2. Perform API call using contextConfig().dateTimeContext for time range
|
|
310
|
+
* 3. Set isLoading to false (resumes auto-refresh counter)
|
|
311
|
+
*/
|
|
312
|
+
private async fetchData(): Promise<void> {
|
|
313
|
+
try {
|
|
314
|
+
this.isLoading.set(true);
|
|
315
|
+
|
|
316
|
+
// In a real widget, you would use the time context for API calls:
|
|
317
|
+
// const { dateFrom, dateTo } = this.contextConfig().dateTimeContext;
|
|
318
|
+
// const data = await this.api.getData({ dateFrom, dateTo });
|
|
319
|
+
|
|
320
|
+
// Simulate API call
|
|
321
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
322
|
+
|
|
323
|
+
this.lastFetchTime = new Date();
|
|
324
|
+
this.fetchCount++;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error('Error fetching data:', error);
|
|
327
|
+
} finally {
|
|
328
|
+
this.isLoading.set(false);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { hookWidget, hookRoute, hookNavigator, NavigatorNode } from '@c8y/ngx-components';
|
|
2
|
+
import {
|
|
3
|
+
GlobalContextSectionComponent,
|
|
4
|
+
hookWidgetConfig
|
|
5
|
+
} from '@c8y/ngx-components/context-dashboard';
|
|
6
|
+
import { GLOBAL_CONTEXT_DASHBOARD_PATHS } from '@c8y/ngx-components/global-context';
|
|
7
|
+
import { gettext } from '@c8y/ngx-components/gettext';
|
|
8
|
+
import { WIDGET_CONTROLS } from './widget-config.model';
|
|
9
|
+
|
|
10
|
+
const WIDGET_ID = 'tutorial.global-context-widget';
|
|
11
|
+
|
|
12
|
+
/** Route path for the example dashboard */
|
|
13
|
+
const DASHBOARD_PATH = 'dashboard/global-context';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Provides the Global Context Widget example.
|
|
17
|
+
*
|
|
18
|
+
* This widget demonstrates the new Global Time Context v2 integration:
|
|
19
|
+
* - Uses GlobalContextConnectorComponent for dashboard mode
|
|
20
|
+
* - Uses LocalControlsComponent for config/view_and_config modes
|
|
21
|
+
* - Handles context changes and refresh events
|
|
22
|
+
* - Manages loading state to pause/resume auto-refresh
|
|
23
|
+
*/
|
|
24
|
+
export function provideGlobalContextWidget() {
|
|
25
|
+
return [
|
|
26
|
+
// Register the dashboard path for Global Context support
|
|
27
|
+
// This enables the global context toolbar on this named dashboard route
|
|
28
|
+
{
|
|
29
|
+
provide: GLOBAL_CONTEXT_DASHBOARD_PATHS,
|
|
30
|
+
useValue: [DASHBOARD_PATH],
|
|
31
|
+
multi: true
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// Register the widget
|
|
35
|
+
hookWidget({
|
|
36
|
+
id: WIDGET_ID,
|
|
37
|
+
label: gettext('Global context widget'),
|
|
38
|
+
description: gettext(
|
|
39
|
+
'Example widget demonstrating Global Time Context v2 integration with GlobalContextConnector and LocalControls.'
|
|
40
|
+
),
|
|
41
|
+
loadComponent: () =>
|
|
42
|
+
import('./global-context-widget-view.component').then(
|
|
43
|
+
m => m.GlobalContextWidgetViewComponent
|
|
44
|
+
),
|
|
45
|
+
loadConfigComponent: () =>
|
|
46
|
+
import('./global-context-widget-config.component').then(
|
|
47
|
+
m => m.GlobalContextWidgetConfigComponent
|
|
48
|
+
),
|
|
49
|
+
data: {
|
|
50
|
+
schema: () => import('c8y-schema-loader?interfaceName=WidgetConfig!./widget-config.model'),
|
|
51
|
+
// Widget controls for view component
|
|
52
|
+
controls: WIDGET_CONTROLS
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
// Register the "Time context" configuration section
|
|
57
|
+
// This injects GlobalContextSectionComponent into the widget configuration panel
|
|
58
|
+
hookWidgetConfig<GlobalContextSectionComponent>({
|
|
59
|
+
widgetId: WIDGET_ID,
|
|
60
|
+
priority: 10,
|
|
61
|
+
label: gettext('Time context'),
|
|
62
|
+
initialState: {
|
|
63
|
+
controls: WIDGET_CONTROLS
|
|
64
|
+
},
|
|
65
|
+
loadComponent: () =>
|
|
66
|
+
import('@c8y/ngx-components/context-dashboard').then(m => m.GlobalContextSectionComponent)
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
// Register route for the example dashboard
|
|
70
|
+
hookRoute({
|
|
71
|
+
path: DASHBOARD_PATH,
|
|
72
|
+
loadComponent: () =>
|
|
73
|
+
import('./global-context-dashboard.component').then(m => m.GlobalContextDashboardComponent)
|
|
74
|
+
}),
|
|
75
|
+
|
|
76
|
+
// Register navigator entry under "Dashboards"
|
|
77
|
+
hookNavigator(
|
|
78
|
+
new NavigatorNode({
|
|
79
|
+
label: gettext('Global context'),
|
|
80
|
+
path: `/${DASHBOARD_PATH}`,
|
|
81
|
+
icon: 'clock1',
|
|
82
|
+
priority: -10,
|
|
83
|
+
parent: 'Dashboards'
|
|
84
|
+
})
|
|
85
|
+
)
|
|
86
|
+
];
|
|
87
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GlobalContextState,
|
|
3
|
+
CONTEXT_FEATURE,
|
|
4
|
+
PresetDefinition
|
|
5
|
+
} from '@c8y/ngx-components/global-context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Widget configuration interface for the Global Context example widget.
|
|
9
|
+
*
|
|
10
|
+
* Extends `GlobalContextState` to inherit all time context properties:
|
|
11
|
+
* - `dateTimeContext`: Contains `dateFrom`, `dateTo`, and `interval`
|
|
12
|
+
* - `refreshOption`: 'live' | 'history' - determines time mode
|
|
13
|
+
* - `isAutoRefreshEnabled`: Whether auto-refresh is active
|
|
14
|
+
* - `refreshInterval`: Auto-refresh interval in milliseconds
|
|
15
|
+
* - `aggregation`: Data aggregation level (MINUTELY, HOURLY, DAILY)
|
|
16
|
+
* - `displayMode`: 'dashboard' | 'config' | 'view_and_config'
|
|
17
|
+
*
|
|
18
|
+
* These properties are automatically managed by `GlobalContextSectionComponent`
|
|
19
|
+
* when configured via `hookWidgetConfig`.
|
|
20
|
+
*/
|
|
21
|
+
export interface WidgetConfig extends GlobalContextState {
|
|
22
|
+
/** Custom widget title displayed in the widget header */
|
|
23
|
+
title?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Widget controls definition shared between widget registration (index.ts)
|
|
28
|
+
* and the view component. Defines which time context features are available
|
|
29
|
+
* in each display mode.
|
|
30
|
+
*
|
|
31
|
+
* Available features:
|
|
32
|
+
* - CONTEXT_FEATURE.LIVE_TIME - Relative time window (e.g., "last 1 hour")
|
|
33
|
+
* - CONTEXT_FEATURE.HISTORY_TIME - Fixed date range picker
|
|
34
|
+
* - CONTEXT_FEATURE.AUTO_REFRESH - Auto-refresh toggle (5s interval)
|
|
35
|
+
* - CONTEXT_FEATURE.AGGREGATION - Data aggregation selector
|
|
36
|
+
* - CONTEXT_FEATURE.REFRESH - Manual refresh button
|
|
37
|
+
*/
|
|
38
|
+
export const WIDGET_CONTROLS: PresetDefinition = {
|
|
39
|
+
dashboard: [
|
|
40
|
+
CONTEXT_FEATURE.LIVE_TIME,
|
|
41
|
+
CONTEXT_FEATURE.HISTORY_TIME,
|
|
42
|
+
CONTEXT_FEATURE.AUTO_REFRESH,
|
|
43
|
+
CONTEXT_FEATURE.AGGREGATION
|
|
44
|
+
],
|
|
45
|
+
config: [
|
|
46
|
+
CONTEXT_FEATURE.LIVE_TIME,
|
|
47
|
+
CONTEXT_FEATURE.HISTORY_TIME,
|
|
48
|
+
CONTEXT_FEATURE.AUTO_REFRESH,
|
|
49
|
+
CONTEXT_FEATURE.AGGREGATION
|
|
50
|
+
],
|
|
51
|
+
view_and_config: [
|
|
52
|
+
CONTEXT_FEATURE.LIVE_TIME,
|
|
53
|
+
CONTEXT_FEATURE.HISTORY_TIME,
|
|
54
|
+
CONTEXT_FEATURE.AUTO_REFRESH,
|
|
55
|
+
CONTEXT_FEATURE.AGGREGATION
|
|
56
|
+
]
|
|
57
|
+
};
|