Skip to content

Commit 1b9226a

Browse files
committed
feat(demo): add temperature widget
1 parent bebecc6 commit 1b9226a

File tree

8 files changed

+553
-3
lines changed

8 files changed

+553
-3
lines changed

projects/demo/i18n-keys.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Demo Application i18n Translation Keys
22

3-
This document lists all the translation keys used in the demo application. The demo app demonstrates comprehensive i18n support with 275 translation keys.
3+
This document lists all the translation keys used in the demo application. The demo app demonstrates comprehensive i18n support with 288 translation keys.
44

55
## Setup Requirements
66

@@ -187,7 +187,7 @@ To use i18n features with the demo application:
187187
- `@@demo.radialGaugeDemo.descriptionBars` - "Bars"
188188
- `@@demo.radialGaugeDemo.descriptionGBUsed` - "GB Used"
189189

190-
### Custom Widgets (Demo App) - 44 keys
190+
### Custom Widgets (Demo App) - 57 keys
191191

192192
#### Realtime Gauge Widget (21 keys)
193193
- `@@demo.widgets.realtimeGauge.name` - "Realtime Gauge"
@@ -257,6 +257,22 @@ To use i18n features with the demo application:
257257
- `@@demo.widgets.sparkline.dialog.responsiveLineColors` - "Responsive line colors"
258258
- `@@demo.widgets.sparkline.dialog.showBackground` - "Show background"
259259

260+
#### Temperature Widget (13 keys)
261+
- `@@demo.widgets.temperature.name` - "Temperature"
262+
- `@@demo.widgets.temperature.description` - "Display a temperature value"
263+
- `@@demo.widgets.temperature.dialog.title` - "Temperature Settings"
264+
- `@@demo.widgets.temperature.dialog.temperatureValue` - "Temperature Value (°C)"
265+
- `@@demo.widgets.temperature.dialog.temperatureValuePlaceholder` - "Enter temperature"
266+
- `@@demo.widgets.temperature.dialog.temperatureHint` - "Value stored in Celsius and converted for display"
267+
- `@@demo.widgets.temperature.dialog.unit` - "Temperature Unit"
268+
- `@@demo.widgets.temperature.dialog.unitCelsius` - "Celsius (°C)"
269+
- `@@demo.widgets.temperature.dialog.unitFahrenheit` - "Fahrenheit (°F)"
270+
- `@@demo.widgets.temperature.dialog.unitKelvin` - "Kelvin (K)"
271+
- `@@demo.widgets.temperature.dialog.label` - "Label (optional)"
272+
- `@@demo.widgets.temperature.dialog.labelPlaceholder` - "e.g., Room Temp, CPU"
273+
- `@@demo.widgets.temperature.dialog.background` - "Background"
274+
- `@@demo.widgets.temperature.dialog.backgroundDescription` - "Adds a background behind the temperature"
275+
260276
### Common Actions (3 keys)
261277
- `@@demo.common.cancel` - "Cancel"
262278
- `@@demo.common.save` - "Save"

projects/demo/public/demo-dashboard.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,32 @@
487487
"maxFontSize": 64,
488488
"templateString": "template string"
489489
}
490+
},
491+
{
492+
"row": 7,
493+
"col": 5,
494+
"rowSpan": 1,
495+
"colSpan": 2,
496+
"widgetTypeid": "@demo/temperature-widget",
497+
"widgetState": {
498+
"temperature": 25,
499+
"unit": "C",
500+
"label": "Server Room",
501+
"hasBackground": true
502+
}
503+
},
504+
{
505+
"row": 7,
506+
"col": 7,
507+
"rowSpan": 1,
508+
"colSpan": 2,
509+
"widgetTypeid": "@demo/temperature-widget",
510+
"widgetState": {
511+
"temperature": 22,
512+
"unit": "K",
513+
"label": "Inference Cave",
514+
"hasBackground": true
515+
}
490516
}
491517
]
492-
}
518+
}

projects/demo/src/app/app.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { SparklineWidgetComponent } from './widgets/sparkline-widget/sparkline-widget.component';
2222
import { SparkbarWidgetComponent } from './widgets/sparkbar-widget/sparkbar-widget.component';
2323
import { RealtimeGaugeWidgetComponent } from './widgets/realtime-gauge-widget/realtime-gauge-widget.component';
24+
import { TemperatureWidgetComponent } from './widgets/temperature-widget/temperature-widget.component';
2425

2526
import { routes } from './app.routes';
2627

@@ -44,6 +45,7 @@ export const appConfig: ApplicationConfig = {
4445
dashboardService.registerWidgetType(RealtimeGaugeWidgetComponent);
4546
dashboardService.registerWidgetType(SparklineWidgetComponent);
4647
dashboardService.registerWidgetType(SparkbarWidgetComponent);
48+
dashboardService.registerWidgetType(TemperatureWidgetComponent);
4749
}),
4850
],
4951
};
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { Component, inject, signal, computed } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
import { FormsModule } from '@angular/forms';
4+
import {
5+
MAT_DIALOG_DATA,
6+
MatDialogRef,
7+
MatDialogModule,
8+
} from '@angular/material/dialog';
9+
import { MatButtonModule } from '@angular/material/button';
10+
import { TemperatureWidgetState } from './temperature-widget.component';
11+
import { MatFormFieldModule } from '@angular/material/form-field';
12+
import { MatSelectModule } from '@angular/material/select';
13+
import { MatInputModule } from '@angular/material/input';
14+
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
15+
16+
@Component({
17+
selector: 'demo-temperature-state-dialog',
18+
standalone: true,
19+
imports: [
20+
CommonModule,
21+
FormsModule,
22+
MatDialogModule,
23+
MatButtonModule,
24+
MatFormFieldModule,
25+
MatSelectModule,
26+
MatInputModule,
27+
MatSlideToggleModule,
28+
],
29+
template: `
30+
<h2 mat-dialog-title i18n="@@demo.widgets.temperature.dialog.title">
31+
Temperature Settings
32+
</h2>
33+
<mat-dialog-content>
34+
<!-- Temperature Value Input -->
35+
<mat-form-field appearance="outline" class="temperature-field">
36+
<mat-label i18n="@@demo.widgets.temperature.dialog.temperatureValue"
37+
>Temperature Value (°C)</mat-label
38+
>
39+
<input
40+
matInput
41+
type="number"
42+
step="0.1"
43+
[ngModel]="temperature()"
44+
(ngModelChange)="onTemperatureChange($event)"
45+
placeholder="{{
46+
temperaturePlaceholder
47+
}}"
48+
i18n-placeholder="@@demo.widgets.temperature.dialog.temperatureValuePlaceholder"
49+
/>
50+
<mat-hint i18n="@@demo.widgets.temperature.dialog.temperatureHint"
51+
>Value stored in Celsius and converted for display</mat-hint
52+
>
53+
</mat-form-field>
54+
55+
<!-- Unit Selection -->
56+
<mat-form-field appearance="outline" class="unit-field">
57+
<mat-label i18n="@@demo.widgets.temperature.dialog.unit"
58+
>Temperature Unit</mat-label
59+
>
60+
<mat-select
61+
[value]="unit()"
62+
(selectionChange)="unit.set($any($event.value))"
63+
>
64+
<mat-option
65+
value="C"
66+
i18n="@@demo.widgets.temperature.dialog.unitCelsius"
67+
>Celsius (°C)</mat-option
68+
>
69+
<mat-option
70+
value="F"
71+
i18n="@@demo.widgets.temperature.dialog.unitFahrenheit"
72+
>Fahrenheit (°F)</mat-option
73+
>
74+
<mat-option
75+
value="K"
76+
i18n="@@demo.widgets.temperature.dialog.unitKelvin"
77+
>Kelvin (K)</mat-option
78+
>
79+
</mat-select>
80+
</mat-form-field>
81+
82+
<!-- Label Input -->
83+
<mat-form-field appearance="outline" class="label-field">
84+
<mat-label i18n="@@demo.widgets.temperature.dialog.label"
85+
>Label (optional)</mat-label
86+
>
87+
<input
88+
matInput
89+
type="text"
90+
maxlength="20"
91+
[(ngModel)]="label"
92+
placeholder="{{
93+
labelPlaceholder
94+
}}"
95+
i18n-placeholder="@@demo.widgets.temperature.dialog.labelPlaceholder"
96+
/>
97+
</mat-form-field>
98+
99+
<!-- Background Toggle -->
100+
<div class="toggle-field">
101+
<mat-slide-toggle
102+
[checked]="hasBackground()"
103+
(change)="hasBackground.set($event.checked)"
104+
i18n="@@demo.widgets.temperature.dialog.background"
105+
>
106+
Background
107+
</mat-slide-toggle>
108+
<span
109+
class="toggle-hint"
110+
i18n="@@demo.widgets.temperature.dialog.backgroundDescription"
111+
>Adds a background behind the temperature</span
112+
>
113+
</div>
114+
</mat-dialog-content>
115+
116+
<mat-dialog-actions align="end">
117+
<button
118+
mat-button
119+
(click)="onCancel()"
120+
i18n="@@demo.common.cancel"
121+
>
122+
Cancel
123+
</button>
124+
<button
125+
mat-flat-button
126+
(click)="save()"
127+
[disabled]="!hasChanged()"
128+
i18n="@@demo.common.save"
129+
>
130+
Save
131+
</button>
132+
</mat-dialog-actions>
133+
`,
134+
styles: [
135+
`
136+
mat-dialog-content {
137+
display: block;
138+
overflow-y: auto;
139+
overflow-x: hidden;
140+
}
141+
142+
mat-form-field {
143+
width: 100%;
144+
display: block;
145+
margin-bottom: 1rem;
146+
}
147+
148+
.temperature-field {
149+
margin-top: 1rem;
150+
}
151+
152+
/* Toggle section */
153+
.toggle-field {
154+
display: flex;
155+
align-items: center;
156+
gap: 0.75rem;
157+
margin-bottom: 0.5rem;
158+
}
159+
160+
.toggle-hint {
161+
margin: 0;
162+
}
163+
`,
164+
],
165+
})
166+
export class TemperatureStateDialogComponent {
167+
private readonly data = inject<TemperatureWidgetState>(MAT_DIALOG_DATA);
168+
private readonly dialogRef = inject(MatDialogRef<TemperatureStateDialogComponent>);
169+
170+
// Placeholders for i18n extraction
171+
readonly temperaturePlaceholder = $localize`:@@demo.widgets.temperature.dialog.temperatureValuePlaceholder:Enter temperature`;
172+
readonly labelPlaceholder = $localize`:@@demo.widgets.temperature.dialog.labelPlaceholder:e.g., Room Temp, CPU`;
173+
174+
// State signals
175+
readonly temperature = signal<number | null>(this.data.temperature ?? null);
176+
readonly unit = signal<'C' | 'F' | 'K'>(this.data.unit ?? 'C');
177+
readonly label = signal<string>(this.data.label ?? '');
178+
readonly hasBackground = signal<boolean>(this.data.hasBackground ?? true);
179+
180+
// Store original values for comparison
181+
private readonly originalTemperature = this.data.temperature ?? null;
182+
private readonly originalUnit = this.data.unit ?? 'C';
183+
private readonly originalLabel = this.data.label ?? '';
184+
private readonly originalHasBackground = this.data.hasBackground ?? true;
185+
186+
// Computed change detection
187+
readonly hasChanged = computed(
188+
() =>
189+
this.temperature() !== this.originalTemperature ||
190+
this.unit() !== this.originalUnit ||
191+
this.label() !== this.originalLabel ||
192+
this.hasBackground() !== this.originalHasBackground
193+
);
194+
195+
onTemperatureChange(value: string | number | null): void {
196+
if (value === '' || value === null) {
197+
this.temperature.set(null);
198+
} else {
199+
const numValue = typeof value === 'string' ? parseFloat(value) : value;
200+
if (!isNaN(numValue)) {
201+
this.temperature.set(numValue);
202+
}
203+
}
204+
}
205+
206+
onCancel(): void {
207+
this.dialogRef.close();
208+
}
209+
210+
save(): void {
211+
this.dialogRef.close({
212+
temperature: this.temperature(),
213+
unit: this.unit(),
214+
label: this.label(),
215+
hasBackground: this.hasBackground(),
216+
} as TemperatureWidgetState);
217+
}
218+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!-- temperature-widget.component.html -->
2+
<div class="temperature-wrapper" [class.has-background]="state().hasBackground">
3+
@if (!hasTemperature()) {
4+
<!-- Show placeholder icon when no temperature is set -->
5+
<div class="svg-placeholder" [innerHTML]="safeSvgIcon"></div>
6+
} @else {
7+
<!-- Show temperature value with optional label -->
8+
<div class="temperature-display">
9+
@if (state().label) {
10+
<div class="label">{{ state().label }}</div>
11+
}
12+
<div class="value-container">
13+
<span
14+
libResponsiveText
15+
[minFontSize]="6"
16+
[maxFontSize]="120"
17+
[lineHeight]="1"
18+
[templateString]="templateString"
19+
>
20+
{{ temperatureValue() }}
21+
</span>
22+
</div>
23+
<div class="unit">{{ unitSymbol() }}</div>
24+
</div>
25+
}
26+
</div>

0 commit comments

Comments
 (0)