@c8y/tutorial 1023.15.0 → 1023.17.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.
@@ -276,6 +276,27 @@ export default {
276
276
  description: 'A sample for breadcrumbs content projection.',
277
277
  scope: 'self'
278
278
  },
279
+ {
280
+ name: 'Resizable example',
281
+ module: 'SplitViewResizableExampleModule',
282
+ path: './src/split-view/resizable-example/split-view-resizable-example.module.ts',
283
+ description: 'A sample for split view with resizable layout.',
284
+ scope: 'self'
285
+ },
286
+ {
287
+ name: 'Fixed example',
288
+ module: 'SplitViewFixedExampleModule',
289
+ path: './src/split-view/fixed-example/split-view-fixed-example.module.ts',
290
+ description: 'A sample for split view with fixed layout.',
291
+ scope: 'self'
292
+ },
293
+ {
294
+ name: 'Split view full width',
295
+ module: 'SplitViewFullWidthExampleModule',
296
+ path: './src/split-view/full-width-example/split-view-full-width-example.module.ts',
297
+ description: 'A sample for split view in full width mode.',
298
+ scope: 'self'
299
+ },
279
300
  {
280
301
  name: 'Component',
281
302
  module: 'ComponentModule',
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@c8y/tutorial",
3
- "version": "1023.15.0",
3
+ "version": "1023.17.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.15.0",
7
- "@c8y/ngx-components": "1023.15.0",
8
- "@c8y/client": "1023.15.0",
9
- "@c8y/bootstrap": "1023.15.0",
6
+ "@c8y/style": "1023.17.0",
7
+ "@c8y/ngx-components": "1023.17.0",
8
+ "@c8y/client": "1023.17.0",
9
+ "@c8y/bootstrap": "1023.17.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.15.0",
18
- "@c8y/devkit": "1023.15.0"
17
+ "@c8y/options": "1023.17.0",
18
+ "@c8y/devkit": "1023.17.0"
19
19
  },
20
20
  "peerDependencies": {
21
21
  "@angular/common": ">=20 <21"
@@ -16,6 +16,8 @@ import { provideMapExampleNavigator } from '../maps';
16
16
  import { provideTranslationsNavigator } from '../translations';
17
17
  import { provideLazyWidget } from '../lazy-widget';
18
18
  import { provideBreadcrumbsNavigator } from '../breadcrumbs';
19
+ import { provideSplitViewSamples } from '../split-view';
20
+ import { provideIconPanelExample } from '../icon-panel';
19
21
  import { provideClientInterceptorSample } from '../client-interceptor';
20
22
  import { provideUserMenuSample } from '../user-menu';
21
23
  import { AlarmsModule } from '@c8y/ngx-components/alarms';
@@ -53,6 +55,8 @@ export const appConfig: ApplicationConfig = {
53
55
  ...provideRedirectToLastRoute(),
54
56
  ...provideAPIMock(),
55
57
  ...provideBreadcrumbsNavigator(),
58
+ ...provideSplitViewSamples(),
59
+ ...provideIconPanelExample(),
56
60
  importProvidersFrom(AlarmsModule.config({ hybrid: false }))
57
61
  ]
58
62
  };
@@ -0,0 +1,20 @@
1
+ <c8y-title>Icon panel examples</c8y-title>
2
+ <div class="p-16">
3
+ <p class="m-b-16 text-bold">Basic usage</p>
4
+ <c8y-icon-panel [sections]="alarmSections"></c8y-icon-panel>
5
+
6
+ <p class="m-t-24 m-b-16 text-bold">Display device details with icons and structured layout.</p>
7
+ <c8y-icon-panel [sections]="deviceSections"></c8y-icon-panel>
8
+
9
+ <p class="m-t-24 m-b-16 text-bold">
10
+ Combine structured sections with additional custom HTML content.
11
+ </p>
12
+ <c8y-icon-panel [sections]="deviceSections.slice(0, 2)">
13
+ <div class="col-xs-12 m-t-8">
14
+ <div class="alert alert-info m-b-0">
15
+ <strong>Tip:</strong>
16
+ You can project additional custom content alongside the icon panels.
17
+ </div>
18
+ </div>
19
+ </c8y-icon-panel>
20
+ </div>
@@ -0,0 +1,108 @@
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { IconPanelComponent, IconPanelSection, TitleComponent } from '@c8y/ngx-components';
4
+
5
+ @Component({
6
+ selector: 'c8y-icon-panel-example',
7
+ templateUrl: './icon-panel-example.component.html',
8
+ standalone: true,
9
+ imports: [CommonModule, IconPanelComponent, TitleComponent]
10
+ })
11
+ export class IconPanelExampleComponent {
12
+ deviceSections: IconPanelSection[] = [
13
+ {
14
+ id: 'device-info',
15
+ label: 'Device Information',
16
+ icon: 'c8y-device',
17
+ visible: true,
18
+ content: `
19
+ <div>
20
+ <p class="m-b-4"><strong>Model:</strong> Smart Thermostat Pro</p>
21
+ <p class="m-b-4"><strong>Serial:</strong> THM-001-2024</p>
22
+ <p class="m-b-0"><strong>Firmware:</strong> v2.4.1</p>
23
+ </div>
24
+ `,
25
+ colClass: 'col-xs-12 col-md-6'
26
+ },
27
+ {
28
+ id: 'location',
29
+ label: 'Location',
30
+ icon: 'map-marker',
31
+ visible: true,
32
+ content: `
33
+ <div>
34
+ <p class="m-b-4">Building A - Floor 1</p>
35
+ <p class="m-b-0"><small class="text-muted">Room 101</small></p>
36
+ </div>
37
+ `,
38
+ colClass: 'col-xs-12 col-md-6'
39
+ },
40
+ {
41
+ id: 'connection',
42
+ label: 'Connection Status',
43
+ icon: 'contactless-payment',
44
+ visible: true,
45
+ content:
46
+ '<p class="m-b-0"><strong class="text-success">Online</strong> - Last seen 2 minutes ago</p>',
47
+ colClass: 'col-xs-12 col-md-4',
48
+ iconClass: 'text-success'
49
+ },
50
+ {
51
+ id: 'battery',
52
+ label: 'Battery Level',
53
+ icon: 'battery-full',
54
+ visible: true,
55
+ content: '<p class="m-b-0"><strong class="text-success">98%</strong> - Charging</p>',
56
+ colClass: 'col-xs-12 col-md-4',
57
+ iconClass: 'text-success'
58
+ },
59
+ {
60
+ id: 'signal',
61
+ label: 'Signal Strength',
62
+ icon: 'signal',
63
+ visible: true,
64
+ content: '<p class="m-b-0"><strong class="text-warning">Medium</strong> - 3/5 bars</p>',
65
+ colClass: 'col-xs-12 col-md-4',
66
+ iconClass: 'text-warning'
67
+ }
68
+ ];
69
+
70
+ alarmSections: IconPanelSection[] = [
71
+ {
72
+ id: 'critical',
73
+ label: 'Critical Alarms',
74
+ icon: 'exclamation-circle',
75
+ visible: true,
76
+ content: '<span class="badge badge-danger">2</span> active',
77
+ colClass: 'col-xs-12 col-sm-6 col-md-3',
78
+ iconClass: 'status critical stroked-icon'
79
+ },
80
+ {
81
+ id: 'major',
82
+ label: 'Major Alarms',
83
+ icon: 'warning',
84
+ visible: true,
85
+ content: '<span class="badge badge-warning">5</span> active',
86
+ colClass: 'col-xs-12 col-sm-6 col-md-3',
87
+ iconClass: 'status major stroked-icon'
88
+ },
89
+ {
90
+ id: 'minor',
91
+ label: 'Minor Alarms',
92
+ icon: 'high-priority',
93
+ visible: true,
94
+ content: '<span class="badge badge-info">12</span> active',
95
+ colClass: 'col-xs-12 col-sm-6 col-md-3',
96
+ iconClass: 'status minor stroked-icon'
97
+ },
98
+ {
99
+ id: 'cleared',
100
+ label: 'Cleared today',
101
+ icon: 'ok',
102
+ visible: true,
103
+ content: '<span class="badge badge-success">28</span> cleared',
104
+ colClass: 'col-xs-12 col-sm-6 col-md-3',
105
+ iconClass: 'text-success'
106
+ }
107
+ ];
108
+ }
@@ -0,0 +1,16 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { RouterModule, Routes } from '@angular/router';
3
+ import { IconPanelExampleComponent } from './icon-panel-example.component';
4
+
5
+ const routes: Routes = [
6
+ {
7
+ path: '',
8
+ component: IconPanelExampleComponent
9
+ }
10
+ ];
11
+
12
+ @NgModule({
13
+ imports: [IconPanelExampleComponent, RouterModule.forChild(routes)],
14
+ exports: [IconPanelExampleComponent]
15
+ })
16
+ export class IconPanelExampleModule {}
@@ -0,0 +1,19 @@
1
+ import { EnvironmentProviders, Provider } from '@angular/core';
2
+ import { NavigatorNode, hookNavigator, hookRoute } from '@c8y/ngx-components';
3
+
4
+ export function provideIconPanelExample(): (Provider | EnvironmentProviders)[] {
5
+ return [
6
+ hookRoute({
7
+ path: 'icon-panel',
8
+ loadChildren: () => import('./icon-panel-example.module').then(m => m.IconPanelExampleModule)
9
+ }),
10
+ hookNavigator(
11
+ new NavigatorNode({
12
+ path: '/icon-panel',
13
+ label: 'Icon panel',
14
+ icon: 'rectangular',
15
+ priority: 250
16
+ })
17
+ )
18
+ ];
19
+ }
@@ -6,7 +6,11 @@ import { ResizableGridComponent, CoreModule } from '@c8y/ngx-components';
6
6
  template: `<c8y-title>Resizable Grid</c8y-title>
7
7
  <div class="p-t-24">
8
8
  <div class="card">
9
- <c8y-resizable-grid style="height: 350px;">
9
+ <c8y-resizable-grid
10
+ style="height: 350px;"
11
+ [collapsible]="collapsible"
12
+ [collapseThreshold]="320"
13
+ >
10
14
  <div left-pane>
11
15
  <div class="card-header">
12
16
  <h4>Left Column</h4>
@@ -28,6 +32,11 @@ import { ResizableGridComponent, CoreModule } from '@c8y/ngx-components';
28
32
  </div>
29
33
  </c8y-resizable-grid>
30
34
  </div>
35
+ <label class="c8y-switch">
36
+ <input type="checkbox" [(ngModel)]="collapsible" />
37
+ <span></span>
38
+ <span class="p-l-8">Enable Collapse Behavior (when width &lt; 320px)</span>
39
+ </label>
31
40
  </div>`,
32
41
  standalone: true,
33
42
  imports: [ResizableGridComponent, CoreModule]
@@ -0,0 +1,97 @@
1
+ <div class="p-l-16 p-r-16">
2
+ <c8y-title>Static example</c8y-title>
3
+
4
+ <c8y-sv
5
+ [isResizable]="false"
6
+ (selectionChange)="onSelectionChange($event)"
7
+ >
8
+ <c8y-sv-list
9
+ [title]="'Tools & utilities'"
10
+ [showEmptyState]="false"
11
+ >
12
+ <c8y-list-group>
13
+ @for (item of items; track item) {
14
+ <c8y-li
15
+ [selectable]="false"
16
+ [c8ySvListItem]="item"
17
+ >
18
+ <c8y-li-icon>
19
+ <i
20
+ class="icon-24"
21
+ [c8yIcon]="item.icon"
22
+ ></i>
23
+ </c8y-li-icon>
24
+
25
+ <c8y-li-body>
26
+ <div class="d-flex a-i-center p-b-4">
27
+ <p class="m-b-0 text-bold text-16 flex-grow">{{ item.name }}</p>
28
+ <span
29
+ class="badge"
30
+ [ngClass]="getStatusClass(item.status)"
31
+ >
32
+ <i
33
+ class="icon-12 m-r-4"
34
+ [c8yIcon]="getStatusIcon(item.status)"
35
+ ></i>
36
+ {{ item.status }}
37
+ </span>
38
+ </div>
39
+ <p class="text-muted small m-b-4">{{ item.type }}</p>
40
+ </c8y-li-body>
41
+ </c8y-li>
42
+ }
43
+ </c8y-list-group>
44
+ <c8y-sv-footer>
45
+ <div class="d-flex justify-content-between">
46
+ <small class="text-muted">{{ items.length }} tools available</small>
47
+ </div>
48
+ </c8y-sv-footer>
49
+ </c8y-sv-list>
50
+ <c8y-sv-details
51
+ emptyStateIcon="cog"
52
+ emptyStateTitle="No tool selected"
53
+ emptyStateSubtitle="Select a tool from the list to view details"
54
+ [title]="'Tool details' | translate"
55
+ >
56
+ @if (selectedItem) {
57
+ <ng-container>
58
+ <div class="d-flex a-i-center m-b-16">
59
+ <i
60
+ class="icon-24 m-r-8"
61
+ [c8yIcon]="selectedItem.icon"
62
+ ></i>
63
+ <h4 class="m-b-0 m-r-8 text-bold">{{ selectedItem.name }}</h4>
64
+ <span
65
+ class="badge"
66
+ [ngClass]="getStatusClass(selectedItem.status)"
67
+ >
68
+ <i
69
+ class="icon-12 m-r-4"
70
+ [c8yIcon]="getStatusIcon(selectedItem.status)"
71
+ ></i>
72
+ {{ selectedItem.status }}
73
+ </span>
74
+ </div>
75
+ <p class="m-b-16">{{ selectedItem.description }}</p>
76
+ <c8y-icon-panel [sections]="itemInfoSections"></c8y-icon-panel>
77
+ </ng-container>
78
+ }
79
+ <c8y-sv-footer>
80
+ <button
81
+ class="btn btn-default btn-sm"
82
+ (click)="editItem()"
83
+ >
84
+ <i class="fa fa-edit"></i>
85
+ Edit
86
+ </button>
87
+ <button
88
+ class="btn btn-danger btn-sm"
89
+ (click)="deleteItem()"
90
+ >
91
+ <i class="fa fa-trash"></i>
92
+ Delete
93
+ </button>
94
+ </c8y-sv-footer>
95
+ </c8y-sv-details>
96
+ </c8y-sv>
97
+ </div>
@@ -0,0 +1,170 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { Component } from '@angular/core';
3
+ import { FormsModule } from '@angular/forms';
4
+ import {
5
+ CoreModule,
6
+ IconDirective,
7
+ IconPanelComponent,
8
+ IconPanelSection,
9
+ ListItemBodyComponent,
10
+ ListItemComponent,
11
+ ListItemIconComponent,
12
+ SplitViewComponent,
13
+ SplitViewDetailsComponent,
14
+ SplitViewFooterComponent,
15
+ SplitViewListComponent,
16
+ SplitViewListItemDirective
17
+ } from '@c8y/ngx-components';
18
+
19
+ @Component({
20
+ selector: 'c8y-split-view-fixed-example',
21
+ templateUrl: './split-view-fixed-example.component.html',
22
+ standalone: true,
23
+ imports: [
24
+ CommonModule,
25
+ FormsModule,
26
+ CoreModule,
27
+ ListItemComponent,
28
+ ListItemIconComponent,
29
+ ListItemBodyComponent,
30
+ IconDirective,
31
+ SplitViewComponent,
32
+ SplitViewListComponent,
33
+ SplitViewDetailsComponent,
34
+ SplitViewFooterComponent,
35
+ IconPanelComponent,
36
+ SplitViewListItemDirective
37
+ ]
38
+ })
39
+ export class SplitViewFixedExampleComponent {
40
+ items = [
41
+ {
42
+ id: 1,
43
+ name: 'Configuration Manager',
44
+ type: 'System Tool',
45
+ status: 'Ready',
46
+ description: 'Manage application settings and preferences',
47
+ icon: 'cog'
48
+ },
49
+ {
50
+ id: 2,
51
+ name: 'Data Analytics',
52
+ type: 'Analytics Tool',
53
+ status: 'Processing',
54
+ description: 'Analyze and visualize your data',
55
+ icon: 'c8y-chart'
56
+ },
57
+ {
58
+ id: 3,
59
+ name: 'User Management',
60
+ type: 'Admin Tool',
61
+ status: 'Active',
62
+ description: 'Manage users, roles and permissions',
63
+ icon: 'c8y-user'
64
+ },
65
+ {
66
+ id: 4,
67
+ name: 'Device Monitor',
68
+ type: 'Monitoring Tool',
69
+ status: 'Connected',
70
+ description: 'Monitor device status and health',
71
+ icon: 'c8y-device'
72
+ },
73
+ {
74
+ id: 5,
75
+ name: 'Report Generator',
76
+ type: 'Reporting Tool',
77
+ status: 'Idle',
78
+ description: 'Generate custom reports and exports',
79
+ icon: 'c8y-report'
80
+ }
81
+ ];
82
+
83
+ selectedItem?: (typeof this.items)[0];
84
+ itemInfoSections: IconPanelSection[] = [];
85
+
86
+ onSelectionChange(item: (typeof this.items)[0] | null) {
87
+ this.selectedItem = item || undefined;
88
+ if (this.selectedItem) {
89
+ this.updateItemInfoSections();
90
+ }
91
+ }
92
+
93
+ getStatusClass(status: string): string {
94
+ switch (status) {
95
+ case 'Ready':
96
+ case 'Active':
97
+ case 'Connected':
98
+ return 'badge-success';
99
+ case 'Processing':
100
+ return 'badge-info';
101
+ case 'Idle':
102
+ return 'badge-warning';
103
+ default:
104
+ return 'badge-system';
105
+ }
106
+ }
107
+
108
+ getStatusIcon(status: string): string {
109
+ switch (status) {
110
+ case 'Ready':
111
+ case 'Active':
112
+ return 'ok';
113
+ case 'Processing':
114
+ return 'refresh';
115
+ case 'Connected':
116
+ return 'connected';
117
+ case 'Idle':
118
+ return 'pause';
119
+ default:
120
+ return 'info-circle';
121
+ }
122
+ }
123
+
124
+ editItem() {
125
+ console.log('Edit item clicked:', this.selectedItem.name);
126
+ console.log('Item details:', this.selectedItem);
127
+ }
128
+
129
+ deleteItem() {
130
+ console.log('Delete item clicked:', this.selectedItem.name);
131
+ console.log('Item ID:', this.selectedItem.id);
132
+ }
133
+
134
+ private updateItemInfoSections() {
135
+ const item = this.selectedItem;
136
+ this.itemInfoSections = [
137
+ {
138
+ id: 'basic-info',
139
+ label: 'Basic Information',
140
+ icon: 'info-circle',
141
+ iconClass: 'text-info',
142
+ visible: true,
143
+ content: `
144
+ <div class="content-flex-20">
145
+ <div class="col-6">
146
+ <div class="d-flex a-i-center gap-4">
147
+ <strong>Tool Type:</strong>
148
+ <span>${item.type}</span>
149
+ </div>
150
+ </div>
151
+ <div class="col-6">
152
+ <div class="d-flex a-i-center gap-4">
153
+ <strong class="flex-no-shrink">Current Status:</strong>
154
+ <span class="badge ${this.getStatusClass(item.status)}">
155
+ <i class="icon-12 m-r-4 dlt-c8y-icon-${this.getStatusIcon(item.status)}"></i>
156
+ ${item.status}
157
+ </span>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ <p class="m-t-8">
162
+ <strong>Description: </strong>
163
+ <span>${item.description}</span>
164
+ </p>
165
+ `,
166
+ colClass: 'col-xs-12'
167
+ }
168
+ ];
169
+ }
170
+ }
@@ -0,0 +1,15 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { NgModule } from '@angular/core';
3
+ import { hookRoute } from '@c8y/ngx-components';
4
+
5
+ @NgModule({
6
+ imports: [CommonModule],
7
+ providers: [
8
+ hookRoute({
9
+ path: 'split-view-fixed',
10
+ loadComponent: () =>
11
+ import('./split-view-fixed-example.component').then(m => m.SplitViewFixedExampleComponent)
12
+ })
13
+ ]
14
+ })
15
+ export class SplitViewFixedExampleModule {}
@@ -0,0 +1,62 @@
1
+ <div class="p-l-16 p-r-16">
2
+ <c8y-title>Single column example</c8y-title>
3
+
4
+ <c8y-sv>
5
+ <c8y-sv-list
6
+ [emptyStateIcon]="'c8y-project'"
7
+ [emptyStateTitle]="'No projects to display'"
8
+ [title]="'Projects'"
9
+ [showEmptyState]="items.length === 0"
10
+ >
11
+ <c8y-sv-header-actions>
12
+ <button class="btn btn-primary btn-sm">
13
+ <i c8yIcon="plus"></i>
14
+ Add Project
15
+ </button>
16
+ </c8y-sv-header-actions>
17
+
18
+ @if (items.length > 0) {
19
+ <c8y-list-group>
20
+ @for (item of items; track item) {
21
+ <c8y-li>
22
+ <c8y-li-body>
23
+ <div class="d-flex a-i-start">
24
+ <div class="flex-grow min-width-0">
25
+ <h4 class="m-b-4">{{ item.name }}</h4>
26
+ <p class="text-muted m-b-8">{{ item.description }}</p>
27
+ <div class="progress">
28
+ <div
29
+ class="progress-bar"
30
+ [style.width.%]="item.progress"
31
+ [ngClass]="getProgressClass(item.progress)"
32
+ >
33
+ {{ item.progress }}%
34
+ </div>
35
+ </div>
36
+ </div>
37
+ <div class="text-right m-l-16">
38
+ <span
39
+ class="badge"
40
+ [ngClass]="getStatusClass(item.status)"
41
+ >
42
+ {{ item.status }}
43
+ </span>
44
+ <br />
45
+ <small class="text-muted m-t-4 d-block">Progress: {{ item.progress }}%</small>
46
+ </div>
47
+ </div>
48
+ </c8y-li-body>
49
+ </c8y-li>
50
+ }
51
+ </c8y-list-group>
52
+ }
53
+
54
+ <c8y-sv-footer>
55
+ <div class="d-flex justify-content-between">
56
+ <small class="text-muted">{{ items.length }} projects total</small>
57
+ <small class="text-muted">Mode: List only</small>
58
+ </div>
59
+ </c8y-sv-footer>
60
+ </c8y-sv-list>
61
+ </c8y-sv>
62
+ </div>
@@ -0,0 +1,86 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { Component } from '@angular/core';
3
+ import { FormsModule } from '@angular/forms';
4
+ import {
5
+ CoreModule,
6
+ SplitViewComponent,
7
+ SplitViewFooterComponent,
8
+ SplitViewHeaderActionsComponent,
9
+ SplitViewListComponent
10
+ } from '@c8y/ngx-components';
11
+
12
+ @Component({
13
+ selector: 'c8y-split-view-full-width-example',
14
+ templateUrl: './split-view-full-width-example.component.html',
15
+ standalone: true,
16
+ imports: [
17
+ CommonModule,
18
+ FormsModule,
19
+ CoreModule,
20
+ SplitViewComponent,
21
+ SplitViewListComponent,
22
+ SplitViewHeaderActionsComponent,
23
+ SplitViewFooterComponent
24
+ ]
25
+ })
26
+ export class SplitViewFullWidthExampleComponent {
27
+ items = [
28
+ {
29
+ id: 1,
30
+ name: 'Project Alpha',
31
+ description: 'Web application development project',
32
+ status: 'In Progress',
33
+ progress: 75
34
+ },
35
+ {
36
+ id: 2,
37
+ name: 'Project Beta',
38
+ description: 'Mobile app development project',
39
+ status: 'Planning',
40
+ progress: 25
41
+ },
42
+ {
43
+ id: 3,
44
+ name: 'Project Gamma',
45
+ description: 'API integration project',
46
+ status: 'Completed',
47
+ progress: 100
48
+ },
49
+ {
50
+ id: 4,
51
+ name: 'Project Delta',
52
+ description: 'Database migration project',
53
+ status: 'In Progress',
54
+ progress: 55
55
+ },
56
+ {
57
+ id: 5,
58
+ name: 'Project Epsilon',
59
+ description: 'Security audit project',
60
+ status: 'On Hold',
61
+ progress: 0
62
+ }
63
+ ];
64
+
65
+ getStatusClass(status: string): string {
66
+ switch (status) {
67
+ case 'Completed':
68
+ return 'badge-success';
69
+ case 'In Progress':
70
+ return 'badge-info';
71
+ case 'Planning':
72
+ return 'badge-system';
73
+ case 'On Hold':
74
+ return 'badge-danger';
75
+ default:
76
+ return 'badge-system';
77
+ }
78
+ }
79
+
80
+ getProgressClass(progress: number): string {
81
+ if (progress === 100) return 'progress-bar-success';
82
+ if (progress >= 75) return 'progress-bar-info';
83
+ if (progress >= 50) return 'progress-bar-warning';
84
+ return 'progress-bar-danger';
85
+ }
86
+ }
@@ -0,0 +1,17 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { NgModule } from '@angular/core';
3
+ import { hookRoute } from '@c8y/ngx-components';
4
+
5
+ @NgModule({
6
+ imports: [CommonModule],
7
+ providers: [
8
+ hookRoute({
9
+ path: 'split-view-full-width',
10
+ loadComponent: () =>
11
+ import('./split-view-full-width-example.component').then(
12
+ m => m.SplitViewFullWidthExampleComponent
13
+ )
14
+ })
15
+ ]
16
+ })
17
+ export class SplitViewFullWidthExampleModule {}
@@ -0,0 +1,42 @@
1
+ import { EnvironmentProviders, Provider } from '@angular/core';
2
+ import { NavigatorNode, hookNavigator, hookRoute } from '@c8y/ngx-components';
3
+
4
+ const root = new NavigatorNode({
5
+ label: 'Split view',
6
+ icon: 'th-list',
7
+ priority: 200
8
+ });
9
+
10
+ root.add(
11
+ new NavigatorNode({
12
+ path: '/split-view/resizable',
13
+ label: 'Basic example',
14
+ icon: 'list'
15
+ })
16
+ );
17
+
18
+ root.add(
19
+ new NavigatorNode({
20
+ path: '/split-view/fixed',
21
+ label: 'Static example',
22
+ icon: 'stop'
23
+ })
24
+ );
25
+
26
+ root.add(
27
+ new NavigatorNode({
28
+ path: '/split-view/full-width',
29
+ label: 'Single column example',
30
+ icon: 'expand'
31
+ })
32
+ );
33
+
34
+ export function provideSplitViewSamples(): (Provider | EnvironmentProviders)[] {
35
+ return [
36
+ hookRoute({
37
+ path: 'split-view',
38
+ loadChildren: () => import('./split-view-lazy.module').then(m => m.SplitViewLazyModule)
39
+ }),
40
+ hookNavigator(root)
41
+ ];
42
+ }
@@ -0,0 +1,188 @@
1
+ <div class="p-l-16 p-r-16">
2
+ <c8y-title>Basic example</c8y-title>
3
+ <c8y-sv
4
+ [resizableConfig]="{ trackId: 'basic-example-split-view', collapsible: false }"
5
+ (selectionChange)="onSelectionChange($event)"
6
+ >
7
+ <c8y-sv-list
8
+ [emptyStateIcon]="'c8y-devices'"
9
+ [title]="'Device list' | translate"
10
+ [emptyStateTitle]="'No devices to display'"
11
+ [loading]="loading"
12
+ [listOpacity]="loading ? 0.6 : 1"
13
+ [showEmptyState]="items.length === 0 && !loading"
14
+ [docsUrl]="'/docs/device-management-application/managing-devices/#device-list'"
15
+ >
16
+ <c8y-sv-header-actions>
17
+ <button
18
+ class="btn btn-default btn-sm"
19
+ (click)="refresh()"
20
+ [disabled]="loading"
21
+ >
22
+ <i
23
+ c8yIcon="refresh"
24
+ [ngClass]="{ 'icon-spin': loading }"
25
+ ></i>
26
+ Refresh
27
+ </button>
28
+ </c8y-sv-header-actions>
29
+
30
+ <!-- Actual content items -->
31
+ @if (!loading && items.length > 0) {
32
+ <c8y-list-group>
33
+ @for (item of items; track item) {
34
+ <c8y-li
35
+ [c8ySvListItem]="item"
36
+ [selectable]="false"
37
+ >
38
+ <c8y-li-icon class="a-s-start">
39
+ <div class="device-icons">
40
+ <i
41
+ class="icon-24 text-gray-dark stroked-icon"
42
+ c8yIcon="c8y-device"
43
+ ></i>
44
+ </div>
45
+ <c8y-device-status
46
+ [mo]="item.managedObject"
47
+ [size]="20"
48
+ ></c8y-device-status>
49
+ </c8y-li-icon>
50
+ <c8y-li-body class="a-s-stretch">
51
+ <div class="d-flex a-i-start fit-h">
52
+ <div class="min-width-0 flex-grow">
53
+ <p class="text-truncate-wrap p-b-4">
54
+ {{ item.name }}
55
+ </p>
56
+ <div class="d-flex">
57
+ <p
58
+ class="small text-muted text-truncate flex-grow"
59
+ [title]="item.location"
60
+ >
61
+ <i c8yIcon="map-marker"></i>
62
+ {{ item.location }}
63
+ </p>
64
+ <span
65
+ class="badge m-l-8"
66
+ [class.badge-success]="item.status === 'Active'"
67
+ [class.badge-info]="item.status === 'Inactive'"
68
+ [class.badge-danger]="item.status === 'Maintenance'"
69
+ >
70
+ {{ item.status }}
71
+ </span>
72
+ </div>
73
+ <p class="small text-muted m-t-4">
74
+ <i c8yIcon="tag"></i>
75
+ {{ item.type }} • {{ item.deviceId }}
76
+ </p>
77
+ </div>
78
+ </div>
79
+ </c8y-li-body>
80
+ </c8y-li>
81
+ }
82
+ </c8y-list-group>
83
+ }
84
+
85
+ <c8y-sv-footer>
86
+ <small class="text-muted">Total devices: {{ items.length }}</small>
87
+ </c8y-sv-footer>
88
+ </c8y-sv-list>
89
+
90
+ <c8y-sv-details
91
+ emptyStateIcon="c8y-device"
92
+ emptyStateTitle="No device selected"
93
+ [title]="'Device details' | translate"
94
+ emptyStateSubtitle="Select a device from the list to view details"
95
+ >
96
+ @if (selectedItem) {
97
+ <c8y-sv-extra-header>
98
+ <div class="d-flex a-i-center gap-4">
99
+ <label class="m-b-0">Device ID</label>
100
+ <p>{{ selectedItem.deviceId }}</p>
101
+ </div>
102
+ </c8y-sv-extra-header>
103
+ }
104
+
105
+ @if (selectedItem) {
106
+ <ng-container>
107
+ <div class="row">
108
+ <div class="col-md-4 m-b-16">
109
+ <label class="text-muted small">Type</label>
110
+ <p>{{ selectedItem.type }}</p>
111
+ </div>
112
+ <div class="col-md-4 m-b-16">
113
+ <label class="text-muted small">Location</label>
114
+ <p>{{ selectedItem.location }}</p>
115
+ </div>
116
+ <div class="col-md-4 m-b-16">
117
+ <label class="text-muted small">Last Seen</label>
118
+ <p>{{ selectedItem.lastSeen }}</p>
119
+ </div>
120
+ </div>
121
+
122
+ <p class="m-t-16 text-bold">Connection Status</p>
123
+ <div class="row d-flex-md m-b-24">
124
+ <div class="col-md-4 col-xs-12">
125
+ <label class="text-muted small">Send Status</label>
126
+ <p
127
+ [ngClass]="getStatusColorClass(selectedItem.managedObject.c8y_Availability?.status)"
128
+ >
129
+ {{ selectedItem.managedObject.c8y_Availability?.status || 'UNKNOWN' }}
130
+ </p>
131
+ </div>
132
+ <div class="col-sm-4 col-xs-12">
133
+ <label class="text-muted small">Push Status</label>
134
+ <p [ngClass]="getStatusColorClass(selectedItem.managedObject.c8y_Connection?.status)">
135
+ {{ selectedItem.managedObject.c8y_Connection?.status || 'UNKNOWN' }}
136
+ </p>
137
+ </div>
138
+ <div class="col-md-4 col-xs-12">
139
+ <label class="text-muted small">Response Interval</label>
140
+ <p>
141
+ {{ selectedItem.managedObject.c8y_RequiredAvailability?.responseInterval || 0 }}s
142
+ </p>
143
+ </div>
144
+ </div>
145
+
146
+ <c8y-sv-details-actions class="m-t-16">
147
+ <button
148
+ class="btn btn-default btn-sm"
149
+ (click)="editDevice()"
150
+ >
151
+ <i c8yIcon="pencil"></i>
152
+ Edit
153
+ </button>
154
+ <button
155
+ class="btn btn-primary btn-sm"
156
+ (click)="restartDevice()"
157
+ >
158
+ <i c8yIcon="refresh"></i>
159
+ Restart
160
+ </button>
161
+ </c8y-sv-details-actions>
162
+ </ng-container>
163
+ }
164
+ <c8y-sv-footer>
165
+ <button
166
+ class="btn btn-default btn-sm"
167
+ (click)="clearSelection()"
168
+ >
169
+ Cancel
170
+ </button>
171
+ <button
172
+ class="btn btn-danger btn-sm"
173
+ (click)="deleteDevice()"
174
+ >
175
+ <i class="fa fa-trash"></i>
176
+ Delete
177
+ </button>
178
+ <button
179
+ class="btn btn-default btn-sm"
180
+ (click)="editDevice()"
181
+ >
182
+ <i class="fa fa-edit"></i>
183
+ Edit
184
+ </button>
185
+ </c8y-sv-footer>
186
+ </c8y-sv-details>
187
+ </c8y-sv>
188
+ </div>
@@ -0,0 +1,214 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { Component, ViewChild } from '@angular/core';
3
+ import {
4
+ CoreModule,
5
+ DeviceStatusComponent,
6
+ EmptyStateComponent,
7
+ IconDirective,
8
+ ListItemBodyComponent,
9
+ ListItemComponent,
10
+ ListItemIconComponent,
11
+ SplitViewComponent,
12
+ SplitViewDetailsActionsComponent,
13
+ SplitViewDetailsComponent,
14
+ SplitViewExtraHeaderComponent,
15
+ SplitViewFooterComponent,
16
+ SplitViewHeaderActionsComponent,
17
+ SplitViewListComponent,
18
+ SplitViewListItemDirective
19
+ } from '@c8y/ngx-components';
20
+
21
+ type DeviceItem = {
22
+ id: number;
23
+ name: string;
24
+ status: string;
25
+ location: string;
26
+ deviceId: string;
27
+ lastSeen: string;
28
+ type: string;
29
+ time: string;
30
+ firstOccurrenceTime: string;
31
+ managedObject: {
32
+ id: string;
33
+ c8y_Availability: { status: string };
34
+ c8y_Connection: { status: string };
35
+ c8y_RequiredAvailability: { responseInterval: number };
36
+ };
37
+ };
38
+
39
+ @Component({
40
+ selector: 'c8y-split-view-resizable-example',
41
+ templateUrl: './split-view-resizable-example.component.html',
42
+ standalone: true,
43
+ imports: [
44
+ CommonModule,
45
+ CoreModule,
46
+ DeviceStatusComponent,
47
+ ListItemComponent,
48
+ ListItemIconComponent,
49
+ ListItemBodyComponent,
50
+ IconDirective,
51
+ EmptyStateComponent,
52
+ SplitViewComponent,
53
+ SplitViewListComponent,
54
+ SplitViewDetailsComponent,
55
+ SplitViewHeaderActionsComponent,
56
+ SplitViewFooterComponent,
57
+ SplitViewExtraHeaderComponent,
58
+ SplitViewDetailsActionsComponent,
59
+ SplitViewListItemDirective
60
+ ]
61
+ })
62
+ export class SplitViewResizableExampleComponent {
63
+ @ViewChild(SplitViewDetailsComponent) detailsComponent?: SplitViewDetailsComponent;
64
+
65
+ loading = false;
66
+ selectedItem?: DeviceItem;
67
+
68
+ items: DeviceItem[] = [
69
+ {
70
+ id: 1,
71
+ name: 'Smart Thermostat Alpha',
72
+ status: 'Active',
73
+ location: 'Building A - Floor 1',
74
+ deviceId: 'THM-001',
75
+ lastSeen: '2 minutes ago',
76
+ type: 'Temperature Sensor',
77
+ time: new Date(Date.now() - 2 * 60 * 1000).toISOString(),
78
+ firstOccurrenceTime: new Date(Date.now() - 2 * 60 * 1000).toISOString(),
79
+ managedObject: {
80
+ id: 'THM-001',
81
+ c8y_Availability: { status: 'AVAILABLE' },
82
+ c8y_Connection: { status: 'CONNECTED' },
83
+ c8y_RequiredAvailability: { responseInterval: 60 }
84
+ }
85
+ },
86
+ {
87
+ id: 2,
88
+ name: 'Security Camera Beta',
89
+ status: 'Inactive',
90
+ location: 'Building B - Lobby',
91
+ deviceId: 'CAM-002',
92
+ lastSeen: '1 hour ago',
93
+ type: 'Security Camera',
94
+ time: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
95
+ firstOccurrenceTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
96
+ managedObject: {
97
+ id: 'CAM-002',
98
+ c8y_Availability: { status: 'UNAVAILABLE' },
99
+ c8y_Connection: { status: 'DISCONNECTED' },
100
+ c8y_RequiredAvailability: { responseInterval: 60 }
101
+ }
102
+ },
103
+ {
104
+ id: 3,
105
+ name: 'Air Quality Monitor Gamma',
106
+ status: 'Active',
107
+ location: 'Building C - Office',
108
+ deviceId: 'AQM-003',
109
+ lastSeen: '30 seconds ago',
110
+ type: 'Environmental Sensor',
111
+ time: new Date(Date.now() - 30 * 1000).toISOString(),
112
+ firstOccurrenceTime: new Date(Date.now() - 30 * 1000).toISOString(),
113
+ managedObject: {
114
+ id: 'AQM-003',
115
+ c8y_Availability: { status: 'AVAILABLE' },
116
+ c8y_Connection: { status: 'CONNECTED' },
117
+ c8y_RequiredAvailability: { responseInterval: 30 }
118
+ }
119
+ },
120
+ {
121
+ id: 4,
122
+ name: 'Smart Lock Delta',
123
+ status: 'Maintenance',
124
+ location: 'Building A - Entrance',
125
+ deviceId: 'LOCK-004',
126
+ lastSeen: '5 minutes ago',
127
+ type: 'Access Control',
128
+ time: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
129
+ firstOccurrenceTime: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
130
+ managedObject: {
131
+ id: 'LOCK-004',
132
+ c8y_Availability: { status: 'MAINTENANCE' },
133
+ c8y_Connection: { status: 'MAINTENANCE' },
134
+ c8y_RequiredAvailability: { responseInterval: 120 }
135
+ }
136
+ },
137
+ {
138
+ id: 5,
139
+ name: 'Energy Meter Epsilon',
140
+ status: 'Active',
141
+ location: 'Building D - Utility Room',
142
+ deviceId: 'EMT-005',
143
+ lastSeen: '1 minute ago',
144
+ type: 'Energy Monitor',
145
+ time: new Date(Date.now() - 60 * 1000).toISOString(),
146
+ firstOccurrenceTime: new Date(Date.now() - 60 * 1000).toISOString(),
147
+ managedObject: {
148
+ id: 'EMT-005',
149
+ c8y_Availability: { status: 'AVAILABLE' },
150
+ c8y_Connection: { status: 'CONNECTED' },
151
+ c8y_RequiredAvailability: { responseInterval: 300 }
152
+ }
153
+ }
154
+ ];
155
+
156
+ onSelectionChange(item: DeviceItem | null): void {
157
+ this.selectedItem = item || undefined;
158
+ }
159
+
160
+ getCurrentDate(): string {
161
+ return new Date().toLocaleDateString();
162
+ }
163
+
164
+ refresh(): void {
165
+ this.loading = true;
166
+ // Simulate API call
167
+ setTimeout(() => {
168
+ this.loading = false;
169
+ }, 1500);
170
+ }
171
+
172
+ editDevice(): void {
173
+ if (!this.selectedItem) return;
174
+ // eslint-disable-next-line no-console
175
+ console.log('Edit device clicked:', this.selectedItem.name);
176
+ // eslint-disable-next-line no-console
177
+ console.log('Device details:', this.selectedItem);
178
+ }
179
+
180
+ restartDevice(): void {
181
+ if (!this.selectedItem) return;
182
+ // eslint-disable-next-line no-console
183
+ console.log('Restart device clicked:', this.selectedItem.name);
184
+ // eslint-disable-next-line no-console
185
+ console.log('Restarting device:', this.selectedItem.deviceId);
186
+ }
187
+
188
+ deleteDevice(): void {
189
+ if (!this.selectedItem) return;
190
+ // eslint-disable-next-line no-console
191
+ console.log('Delete device clicked:', this.selectedItem.name);
192
+ // eslint-disable-next-line no-console
193
+ console.log('Device ID:', this.selectedItem.deviceId);
194
+ }
195
+
196
+ clearSelection(): void {
197
+ this.detailsComponent?.clearSelection();
198
+ }
199
+
200
+ getStatusColorClass(status: string): string {
201
+ switch (status) {
202
+ case 'AVAILABLE':
203
+ case 'CONNECTED':
204
+ return 'text-success';
205
+ case 'UNAVAILABLE':
206
+ case 'DISCONNECTED':
207
+ return 'text-danger';
208
+ case 'MAINTENANCE':
209
+ return 'text-muted';
210
+ default:
211
+ return 'text-muted';
212
+ }
213
+ }
214
+ }
@@ -0,0 +1,17 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { NgModule } from '@angular/core';
3
+ import { hookRoute } from '@c8y/ngx-components';
4
+
5
+ @NgModule({
6
+ imports: [CommonModule],
7
+ providers: [
8
+ hookRoute({
9
+ path: 'split-view-resizable',
10
+ loadComponent: () =>
11
+ import('./split-view-resizable-example.component').then(
12
+ m => m.SplitViewResizableExampleComponent
13
+ )
14
+ })
15
+ ]
16
+ })
17
+ export class SplitViewResizableExampleModule {}
@@ -0,0 +1,37 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { NgModule } from '@angular/core';
3
+ import { RouterModule, Routes } from '@angular/router';
4
+
5
+ const routes: Routes = [
6
+ {
7
+ path: 'resizable',
8
+ loadComponent: () =>
9
+ import('./resizable-example/split-view-resizable-example.component').then(
10
+ m => m.SplitViewResizableExampleComponent
11
+ )
12
+ },
13
+ {
14
+ path: 'fixed',
15
+ loadComponent: () =>
16
+ import('./fixed-example/split-view-fixed-example.component').then(
17
+ m => m.SplitViewFixedExampleComponent
18
+ )
19
+ },
20
+ {
21
+ path: 'full-width',
22
+ loadComponent: () =>
23
+ import('./full-width-example/split-view-full-width-example.component').then(
24
+ m => m.SplitViewFullWidthExampleComponent
25
+ )
26
+ },
27
+ {
28
+ path: '',
29
+ redirectTo: 'resizable',
30
+ pathMatch: 'full'
31
+ }
32
+ ];
33
+
34
+ @NgModule({
35
+ imports: [CommonModule, RouterModule.forChild(routes)]
36
+ })
37
+ export class SplitViewLazyModule {}