@c8y/tutorial 1023.48.0 → 1023.48.2
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/__mocks/scoped-mocks/context-dashboard.ts +3 -1
- package/src/__mocks/utils/generators/managedObjects.ts +2 -2
- package/src/dashboard/widget-guide-context-dashboard/widget-guide-context-dashboard.component.ts +11 -3
- package/src/widget/demo-widget-config.component.ts +69 -66
- package/src/widget/demo-widget.component.ts +29 -39
- package/src/widget/index.ts +3 -22
- package/src/widget/widget-config.model.ts +5 -2
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c8y/tutorial",
|
|
3
|
-
"version": "1023.48.
|
|
3
|
+
"version": "1023.48.2",
|
|
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.48.
|
|
7
|
-
"@c8y/ngx-components": "1023.48.
|
|
8
|
-
"@c8y/client": "1023.48.
|
|
9
|
-
"@c8y/bootstrap": "1023.48.
|
|
6
|
+
"@c8y/style": "1023.48.2",
|
|
7
|
+
"@c8y/ngx-components": "1023.48.2",
|
|
8
|
+
"@c8y/client": "1023.48.2",
|
|
9
|
+
"@c8y/bootstrap": "1023.48.2",
|
|
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.48.
|
|
18
|
-
"@c8y/devkit": "1023.48.
|
|
17
|
+
"@c8y/options": "1023.48.2",
|
|
18
|
+
"@c8y/devkit": "1023.48.2"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@angular/common": ">=20 <21"
|
|
@@ -5,7 +5,9 @@ import { generateResponse, handleRequest } from '../utils/common';
|
|
|
5
5
|
import { generateDashboard } from '../../__mocks/utils/generators/managedObjects';
|
|
6
6
|
|
|
7
7
|
export class ContextDashboardInterceptor implements HttpInterceptor {
|
|
8
|
-
dashboard = generateDashboard(
|
|
8
|
+
dashboard = generateDashboard({
|
|
9
|
+
device: { id: '12345', name: 'Demo Sensor Device' }
|
|
10
|
+
});
|
|
9
11
|
|
|
10
12
|
intercept(req: ApiCall, next: HttpHandler): Observable<IFetchResponse> {
|
|
11
13
|
return handleRequest(req, next, 'inventory/managedObjects', {
|
|
@@ -172,10 +172,10 @@ export function generateDashboard({
|
|
|
172
172
|
_x: 3,
|
|
173
173
|
_y: 0,
|
|
174
174
|
id: id,
|
|
175
|
-
title: '
|
|
175
|
+
title: 'Demo Widget Example',
|
|
176
176
|
_width: 6,
|
|
177
177
|
config: {
|
|
178
|
-
text: '
|
|
178
|
+
text: 'This text is configured via the widget settings. Click the edit button to change it!'
|
|
179
179
|
},
|
|
180
180
|
_height: 6
|
|
181
181
|
}
|
package/src/dashboard/widget-guide-context-dashboard/widget-guide-context-dashboard.component.ts
CHANGED
|
@@ -17,6 +17,14 @@ import { ContextDashboardModule } from '@c8y/ngx-components/context-dashboard';
|
|
|
17
17
|
imports: [ContextDashboardModule, CoreModule, CommonModule]
|
|
18
18
|
})
|
|
19
19
|
export class WidgetGuideContextDashboardComponent {
|
|
20
|
+
/**
|
|
21
|
+
* Default widgets shown when no dashboard exists in the backend.
|
|
22
|
+
*
|
|
23
|
+
* Note: In the codex environment, the mock interceptor at
|
|
24
|
+
* `packages/tutorial/src/__mocks/utils/generators/managedObjects.ts`
|
|
25
|
+
* returns a fake dashboard, so these defaults are not used.
|
|
26
|
+
* If you change these values, also update `generateDashboard()` in the mock.
|
|
27
|
+
*/
|
|
20
28
|
defaultWidgets: Widget[] = [
|
|
21
29
|
{
|
|
22
30
|
_x: 3,
|
|
@@ -25,10 +33,10 @@ export class WidgetGuideContextDashboardComponent {
|
|
|
25
33
|
_height: 6,
|
|
26
34
|
componentId: 'angular.widget.demo',
|
|
27
35
|
config: {
|
|
28
|
-
text: '
|
|
36
|
+
text: 'This text is configured via the widget settings. Click the edit button to change it!'
|
|
29
37
|
},
|
|
30
|
-
title: '
|
|
31
|
-
id: '
|
|
38
|
+
title: 'Demo Widget Example1',
|
|
39
|
+
id: 'demo_widget_example'
|
|
32
40
|
}
|
|
33
41
|
];
|
|
34
42
|
}
|
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ControlContainer, NgForm, FormsModule } from '@angular/forms';
|
|
1
|
+
import { AsyncPipe } from '@angular/common';
|
|
3
2
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
Component,
|
|
4
|
+
DestroyRef,
|
|
5
|
+
inject,
|
|
6
|
+
Input,
|
|
7
|
+
OnInit,
|
|
8
|
+
TemplateRef,
|
|
9
|
+
ViewChild
|
|
10
|
+
} from '@angular/core';
|
|
11
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
12
|
+
import {
|
|
13
|
+
ControlContainer,
|
|
14
|
+
FormBuilder,
|
|
15
|
+
FormControl,
|
|
16
|
+
FormGroup,
|
|
17
|
+
NgForm,
|
|
18
|
+
ReactiveFormsModule,
|
|
19
|
+
Validators
|
|
20
|
+
} from '@angular/forms';
|
|
21
|
+
import { AlertService, DynamicComponent, FormGroupComponent } from '@c8y/ngx-components';
|
|
22
|
+
import { WidgetConfigService } from '@c8y/ngx-components/context-dashboard';
|
|
23
|
+
import { BehaviorSubject } from 'rxjs';
|
|
24
|
+
import { WidgetDemo } from './demo-widget.component';
|
|
11
25
|
import { WidgetConfig } from './widget-config.model';
|
|
12
26
|
|
|
13
27
|
@Component({
|
|
@@ -16,75 +30,64 @@ import { WidgetConfig } from './widget-config.model';
|
|
|
16
30
|
<div class="form-group">
|
|
17
31
|
<c8y-form-group>
|
|
18
32
|
<label>Text</label>
|
|
19
|
-
<textarea
|
|
20
|
-
style="width: 100%"
|
|
21
|
-
name="text"
|
|
22
|
-
[(ngModel)]="config.text"
|
|
23
|
-
[required]="true"
|
|
24
|
-
></textarea>
|
|
33
|
+
<textarea style="width: 100%" [formControl]="formGroup.controls.text"></textarea>
|
|
25
34
|
</c8y-form-group>
|
|
26
|
-
|
|
27
|
-
<label
|
|
28
|
-
>Configuration
|
|
29
|
-
<button class="btn btn-primary btn-xs" type="button" (click)="more = !more">
|
|
30
|
-
{{ more ? 'Hide' : 'Show' }} settings
|
|
31
|
-
</button></label
|
|
32
|
-
>
|
|
33
|
-
<pre><code>{{ clean(config, more) | json }}</code></pre>
|
|
34
35
|
</div>
|
|
36
|
+
|
|
37
|
+
<ng-template #widgetPreview>
|
|
38
|
+
<c8y-widget-demo [config]="config$ | async"></c8y-widget-demo>
|
|
39
|
+
</ng-template>
|
|
35
40
|
`,
|
|
36
|
-
// We connect our parent Form to this form (for disabling the save button)
|
|
37
|
-
// you can also enable the button by using ContextServiceDashboard.formDisabled
|
|
38
|
-
// property instead (by default it is enabled).
|
|
39
41
|
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
|
|
40
42
|
standalone: true,
|
|
41
|
-
imports: [FormGroupComponent,
|
|
43
|
+
imports: [FormGroupComponent, ReactiveFormsModule, WidgetDemo, AsyncPipe]
|
|
42
44
|
})
|
|
43
|
-
export class WidgetConfigDemo implements DynamicComponent,
|
|
44
|
-
/**
|
|
45
|
-
* The configuration which is shared between configuration component and display component.
|
|
46
|
-
* Should be searilzabled to allow to save it to the API. The config is saved automatically
|
|
47
|
-
* to the API on "save"-button hit. The onBeforeSave handler can be used to change this behavior,
|
|
48
|
-
* or to manipulate the object.
|
|
49
|
-
*
|
|
50
|
-
* Note: The dashboard itself adds certain properties. As such, some properties are not allowed (e.g. device or settings)
|
|
51
|
-
*/
|
|
45
|
+
export class WidgetConfigDemo implements DynamicComponent, OnInit {
|
|
46
|
+
/** Configuration passed by the dashboard framework. */
|
|
52
47
|
@Input() config: WidgetConfig = {};
|
|
53
48
|
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
*/
|
|
57
|
-
more = false;
|
|
49
|
+
/** Reactive form group for the widget configuration. */
|
|
50
|
+
formGroup: FormGroup<{ text: FormControl<string | null> }>;
|
|
58
51
|
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
*/
|
|
62
|
-
constructor(private alert: AlertService) {}
|
|
52
|
+
/** Emits config changes for the preview template. */
|
|
53
|
+
config$ = new BehaviorSubject<WidgetConfig>({});
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.alert.warning('Please enter a valid text.');
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
this.config.widgetInstanceGlobalTimeContext = true;
|
|
73
|
-
this.config.canDecoupleGlobalTimeContext = true;
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
55
|
+
private readonly alert = inject(AlertService);
|
|
56
|
+
private readonly widgetConfigService = inject(WidgetConfigService);
|
|
57
|
+
private readonly formBuilder = inject(FormBuilder);
|
|
58
|
+
private readonly form = inject(NgForm);
|
|
59
|
+
private readonly destroyRef = inject(DestroyRef);
|
|
76
60
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
clean(config, more) {
|
|
81
|
-
if (more) {
|
|
82
|
-
return config;
|
|
83
|
-
}
|
|
84
|
-
return omit(config, 'settings');
|
|
61
|
+
@ViewChild('widgetPreview')
|
|
62
|
+
set preview(template: TemplateRef<any>) {
|
|
63
|
+
this.widgetConfigService.setPreview(template ?? null);
|
|
85
64
|
}
|
|
86
65
|
|
|
87
|
-
|
|
88
|
-
|
|
66
|
+
ngOnInit(): void {
|
|
67
|
+
// Create form with initial values from config
|
|
68
|
+
this.formGroup = this.formBuilder.group({
|
|
69
|
+
text: [this.config?.text || '', Validators.required]
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Register form with parent NgForm for validation
|
|
73
|
+
this.form.form.addControl('widgetConfig', this.formGroup);
|
|
74
|
+
|
|
75
|
+
// Initialize preview
|
|
76
|
+
this.config$.next(this.config);
|
|
77
|
+
|
|
78
|
+
// Update preview when form values change
|
|
79
|
+
this.formGroup.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
|
|
80
|
+
this.config$.next({ ...this.config, ...value });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Register save callback - validates and merges form values into config
|
|
84
|
+
this.widgetConfigService.addOnBeforeSave(config => {
|
|
85
|
+
if (this.formGroup.invalid) {
|
|
86
|
+
this.alert.warning('Please enter a valid text.');
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
Object.assign(config, this.formGroup.value);
|
|
90
|
+
return true;
|
|
91
|
+
});
|
|
89
92
|
}
|
|
90
93
|
}
|
|
@@ -1,65 +1,55 @@
|
|
|
1
|
-
import { Component,
|
|
2
|
-
import { gettext } from '@c8y/ngx-components/gettext';
|
|
1
|
+
import { Component, computed, input, OnInit } from '@angular/core';
|
|
3
2
|
import {
|
|
4
3
|
DismissAlertStrategy,
|
|
5
4
|
DynamicComponentAlert,
|
|
6
|
-
DynamicComponentAlertAggregator
|
|
7
|
-
DatePipe
|
|
5
|
+
DynamicComponentAlertAggregator
|
|
8
6
|
} from '@c8y/ngx-components';
|
|
9
|
-
import { NgIf } from '@angular/common';
|
|
10
7
|
import { WidgetConfig } from './widget-config.model';
|
|
11
8
|
|
|
12
9
|
@Component({
|
|
13
10
|
selector: 'c8y-widget-demo',
|
|
14
11
|
template: `
|
|
15
12
|
<div class="p-16">
|
|
16
|
-
<h1>
|
|
17
|
-
<p class="text">
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
<li>To: {{ (config?.date[1] | c8yDate) || 'No time context' }}</li>
|
|
25
|
-
</ul>
|
|
26
|
-
</ng-container>
|
|
27
|
-
<button class="btn btn-default" (click)="addAlert()">Add warning alert</button>
|
|
13
|
+
<h1>Demo Widget</h1>
|
|
14
|
+
<p class="text">{{ displayText() }}</p>
|
|
15
|
+
@if (deviceName()) {
|
|
16
|
+
<small>Device: {{ deviceName() }}</small>
|
|
17
|
+
}
|
|
18
|
+
<div class="m-t-16">
|
|
19
|
+
<button class="btn btn-default btn-sm" (click)="showAlert()">Show alert</button>
|
|
20
|
+
</div>
|
|
28
21
|
</div>
|
|
29
22
|
`,
|
|
30
23
|
styles: [
|
|
31
24
|
`
|
|
32
25
|
.text {
|
|
33
|
-
font-size:
|
|
26
|
+
font-size: 1.5em;
|
|
27
|
+
color: var(--c8y-brand-primary);
|
|
34
28
|
}
|
|
35
29
|
`
|
|
36
30
|
],
|
|
37
|
-
standalone: true
|
|
38
|
-
imports: [NgIf, DatePipe]
|
|
31
|
+
standalone: true
|
|
39
32
|
})
|
|
40
|
-
export class WidgetDemo implements
|
|
41
|
-
|
|
42
|
-
alerts: DynamicComponentAlertAggregator;
|
|
33
|
+
export class WidgetDemo implements OnInit {
|
|
34
|
+
readonly config = input<WidgetConfig>();
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
/** Computed signal for display text with fallback */
|
|
37
|
+
readonly displayText = computed(() => this.config()?.text || 'No text configured');
|
|
38
|
+
|
|
39
|
+
/** Computed signal for device name */
|
|
40
|
+
readonly deviceName = computed(() => this.config()?.device?.name);
|
|
41
|
+
|
|
42
|
+
/** Set by the dashboard framework - used to display alerts on the widget */
|
|
43
|
+
alerts: DynamicComponentAlertAggregator;
|
|
50
44
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
45
|
+
ngOnInit(): void {
|
|
46
|
+
// Enable dismissible alerts for warning type
|
|
47
|
+
this.alerts?.setAlertGroupDismissStrategy('warning', DismissAlertStrategy.TEMPORARY);
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
this.alerts
|
|
59
|
-
new DynamicComponentAlert({
|
|
60
|
-
type: 'warning',
|
|
61
|
-
text: gettext('Operation not supported by this device.')
|
|
62
|
-
})
|
|
50
|
+
showAlert(): void {
|
|
51
|
+
this.alerts?.addAlerts(
|
|
52
|
+
new DynamicComponentAlert({ type: 'warning', text: 'This is a dismissible demo alert!' })
|
|
63
53
|
);
|
|
64
54
|
}
|
|
65
55
|
}
|
package/src/widget/index.ts
CHANGED
|
@@ -4,36 +4,17 @@ import { WidgetConfigDemo } from './demo-widget-config.component';
|
|
|
4
4
|
|
|
5
5
|
export function provideDemoWidget() {
|
|
6
6
|
return [
|
|
7
|
-
/**
|
|
8
|
-
* This demo widget provides an example on how
|
|
9
|
-
* to use the hookWidget. The component itself
|
|
10
|
-
* is implemented in the dashboard on the
|
|
11
|
-
* ../hello/hello.component.html by using the
|
|
12
|
-
* dynamic-component tag.
|
|
13
|
-
*/
|
|
14
7
|
hookWidget({
|
|
15
8
|
id: 'angular.widget.demo',
|
|
16
|
-
label: '
|
|
17
|
-
description: '
|
|
9
|
+
label: 'Demo Widget',
|
|
10
|
+
description: 'A simple demo widget showing text and device context',
|
|
18
11
|
component: WidgetDemo,
|
|
19
12
|
configComponent: WidgetConfigDemo,
|
|
20
13
|
errorStrategy: DynamicComponentErrorStrategy.OVERLAY_ERROR,
|
|
21
|
-
/** new Angular-Dashboard definition */
|
|
22
14
|
data: {
|
|
23
15
|
schema: () => import('c8y-schema-loader?interfaceName=WidgetConfig!./widget-config.model'),
|
|
24
|
-
// The settings object can be used to configure the configComponent
|
|
25
16
|
settings: {
|
|
26
|
-
noNewWidgets: false
|
|
27
|
-
ng1: {
|
|
28
|
-
options: {
|
|
29
|
-
noDeviceTarget: false, // Set this to true to hide the AngularJS device selector.
|
|
30
|
-
groupsSelectable: false // Set this, if not only devices should be selectable.
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
// Settings that are attached to the display component (in this case: WidgetDemo)
|
|
35
|
-
displaySettings: {
|
|
36
|
-
globalTimeContext: true // Set this to true, to add a global time context binding
|
|
17
|
+
noNewWidgets: false
|
|
37
18
|
}
|
|
38
19
|
}
|
|
39
20
|
})
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IIdentified, IManagedObject } from '@c8y/client';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export interface WidgetConfig {
|
|
4
|
+
text?: string;
|
|
5
|
+
device?: IIdentified & Partial<IManagedObject>;
|
|
6
|
+
}
|