@dragonworks/ngx-dashboard-widgets 20.0.4 → 20.0.6
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/ng-package.json +7 -0
- package/package.json +31 -42
- package/src/lib/arrow-widget/arrow-state-dialog.component.ts +187 -0
- package/src/lib/arrow-widget/arrow-widget.component.html +9 -0
- package/src/lib/arrow-widget/arrow-widget.component.scss +52 -0
- package/src/lib/arrow-widget/arrow-widget.component.ts +78 -0
- package/src/lib/arrow-widget/arrow-widget.metadata.ts +3 -0
- package/src/lib/clock-widget/analog-clock/analog-clock.component.html +66 -0
- package/src/lib/clock-widget/analog-clock/analog-clock.component.scss +103 -0
- package/src/lib/clock-widget/analog-clock/analog-clock.component.ts +120 -0
- package/src/lib/clock-widget/clock-state-dialog.component.ts +170 -0
- package/src/lib/clock-widget/clock-widget.component.html +16 -0
- package/src/lib/clock-widget/clock-widget.component.scss +160 -0
- package/src/lib/clock-widget/clock-widget.component.ts +87 -0
- package/src/lib/clock-widget/clock-widget.metadata.ts +42 -0
- package/src/lib/clock-widget/digital-clock/__tests__/digital-clock.component.spec.ts +276 -0
- package/src/lib/clock-widget/digital-clock/digital-clock.component.html +1 -0
- package/src/lib/clock-widget/digital-clock/digital-clock.component.scss +43 -0
- package/src/lib/clock-widget/digital-clock/digital-clock.component.ts +105 -0
- package/src/lib/directives/__tests__/responsive-text.directive.spec.ts +906 -0
- package/src/lib/directives/responsive-text.directive.ts +334 -0
- package/src/lib/label-widget/__tests__/label-widget.component.spec.ts +539 -0
- package/src/lib/label-widget/label-state-dialog.component.ts +385 -0
- package/src/lib/label-widget/label-widget.component.html +21 -0
- package/src/lib/label-widget/label-widget.component.scss +112 -0
- package/src/lib/label-widget/label-widget.component.ts +96 -0
- package/src/lib/label-widget/label-widget.metadata.ts +3 -0
- package/src/public-api.ts +7 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +14 -0
- package/fesm2022/dragonworks-ngx-dashboard-widgets.mjs +0 -1255
- package/fesm2022/dragonworks-ngx-dashboard-widgets.mjs.map +0 -1
- package/index.d.ts +0 -147
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Component, ChangeDetectionStrategy, input, inject, signal, computed, DestroyRef, viewChild, ElementRef, Renderer2, effect } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'ngx-analog-clock',
|
|
5
|
+
standalone: true,
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
templateUrl: './analog-clock.component.html',
|
|
8
|
+
styleUrl: './analog-clock.component.scss',
|
|
9
|
+
host: {
|
|
10
|
+
'[class.has-background]': 'hasBackground()',
|
|
11
|
+
'[class.show-seconds]': 'showSeconds()',
|
|
12
|
+
'class': 'clock-widget analog'
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
export class AnalogClockComponent {
|
|
16
|
+
readonly #destroyRef = inject(DestroyRef);
|
|
17
|
+
readonly #renderer = inject(Renderer2);
|
|
18
|
+
|
|
19
|
+
// Inputs
|
|
20
|
+
hasBackground = input<boolean>(false);
|
|
21
|
+
showSeconds = input<boolean>(true);
|
|
22
|
+
|
|
23
|
+
// ViewChild references for clock hands
|
|
24
|
+
hourHand = viewChild<ElementRef<SVGPathElement>>('hourHand');
|
|
25
|
+
minuteHand = viewChild<ElementRef<SVGPathElement>>('minuteHand');
|
|
26
|
+
secondHand = viewChild<ElementRef<SVGPathElement>>('secondHand');
|
|
27
|
+
|
|
28
|
+
// Time tracking
|
|
29
|
+
currentTime = signal(new Date());
|
|
30
|
+
|
|
31
|
+
// Computed rotation signals
|
|
32
|
+
secondHandRotation = computed(() => {
|
|
33
|
+
const seconds = this.currentTime().getSeconds();
|
|
34
|
+
return seconds * 6; // 360° / 60s = 6° per second
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
minuteHandRotation = computed(() => {
|
|
38
|
+
const time = this.currentTime();
|
|
39
|
+
const minutes = time.getMinutes();
|
|
40
|
+
const seconds = time.getSeconds();
|
|
41
|
+
return minutes * 6 + seconds / 10; // Smooth minute hand movement
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
hourHandRotation = computed(() => {
|
|
45
|
+
const time = this.currentTime();
|
|
46
|
+
const hours = time.getHours() % 12;
|
|
47
|
+
const minutes = time.getMinutes();
|
|
48
|
+
const seconds = time.getSeconds();
|
|
49
|
+
return hours * 30 + minutes / 2 + seconds / 120; // Smooth hour hand movement
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
#intervalId: number | null = null;
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
// Set up time update timer
|
|
56
|
+
this.#startTimer();
|
|
57
|
+
|
|
58
|
+
// Clean up timer on component destruction
|
|
59
|
+
this.#destroyRef.onDestroy(() => {
|
|
60
|
+
this.#stopTimer();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Update DOM when rotations change
|
|
64
|
+
effect(() => {
|
|
65
|
+
this.#updateClockHands();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#startTimer(): void {
|
|
70
|
+
// Sync to the next second boundary for smooth start
|
|
71
|
+
const now = new Date();
|
|
72
|
+
const msUntilNextSecond = 1000 - now.getMilliseconds();
|
|
73
|
+
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
this.currentTime.set(new Date());
|
|
76
|
+
|
|
77
|
+
// Start the regular 1-second interval
|
|
78
|
+
this.#intervalId = window.setInterval(() => {
|
|
79
|
+
this.currentTime.set(new Date());
|
|
80
|
+
}, 1000);
|
|
81
|
+
}, msUntilNextSecond);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#stopTimer(): void {
|
|
85
|
+
if (this.#intervalId !== null) {
|
|
86
|
+
clearInterval(this.#intervalId);
|
|
87
|
+
this.#intervalId = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#updateClockHands(): void {
|
|
92
|
+
const hourElement = this.hourHand()?.nativeElement;
|
|
93
|
+
const minuteElement = this.minuteHand()?.nativeElement;
|
|
94
|
+
const secondElement = this.secondHand()?.nativeElement;
|
|
95
|
+
|
|
96
|
+
if (hourElement) {
|
|
97
|
+
this.#renderer.setAttribute(
|
|
98
|
+
hourElement,
|
|
99
|
+
'transform',
|
|
100
|
+
`rotate(${this.hourHandRotation()}, 400, 400)`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (minuteElement) {
|
|
105
|
+
this.#renderer.setAttribute(
|
|
106
|
+
minuteElement,
|
|
107
|
+
'transform',
|
|
108
|
+
`rotate(${this.minuteHandRotation()}, 400, 400)`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (secondElement && this.showSeconds()) {
|
|
113
|
+
this.#renderer.setAttribute(
|
|
114
|
+
secondElement,
|
|
115
|
+
'transform',
|
|
116
|
+
`rotate(${this.secondHandRotation()}, 400, 400)`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
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 { MatRadioModule } from '@angular/material/radio';
|
|
11
|
+
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
12
|
+
import { ClockWidgetState } from './clock-widget.component';
|
|
13
|
+
|
|
14
|
+
@Component({
|
|
15
|
+
selector: 'demo-clock-state-dialog',
|
|
16
|
+
standalone: true,
|
|
17
|
+
imports: [
|
|
18
|
+
CommonModule,
|
|
19
|
+
FormsModule,
|
|
20
|
+
MatDialogModule,
|
|
21
|
+
MatButtonModule,
|
|
22
|
+
MatRadioModule,
|
|
23
|
+
MatSlideToggleModule,
|
|
24
|
+
],
|
|
25
|
+
template: `
|
|
26
|
+
<h2 mat-dialog-title>Clock Settings</h2>
|
|
27
|
+
<mat-dialog-content>
|
|
28
|
+
<div class="mode-selection">
|
|
29
|
+
<label class="section-label">Display Mode</label>
|
|
30
|
+
<mat-radio-group
|
|
31
|
+
[value]="mode()"
|
|
32
|
+
(change)="mode.set($any($event.value))"
|
|
33
|
+
>
|
|
34
|
+
<mat-radio-button value="digital">Digital</mat-radio-button>
|
|
35
|
+
<mat-radio-button value="analog">Analog</mat-radio-button>
|
|
36
|
+
</mat-radio-group>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- Time Format (only for digital mode) -->
|
|
40
|
+
@if (mode() === 'digital') {
|
|
41
|
+
<div class="format-selection">
|
|
42
|
+
<label class="section-label">Time Format</label>
|
|
43
|
+
<mat-radio-group
|
|
44
|
+
[value]="timeFormat()"
|
|
45
|
+
(change)="timeFormat.set($any($event.value))"
|
|
46
|
+
>
|
|
47
|
+
<mat-radio-button value="24h">24 Hour (14:30:45)</mat-radio-button>
|
|
48
|
+
<mat-radio-button value="12h">12 Hour (2:30:45 PM)</mat-radio-button>
|
|
49
|
+
</mat-radio-group>
|
|
50
|
+
</div>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
<!-- Show Seconds Toggle (for both digital and analog modes) -->
|
|
54
|
+
<div class="toggle-section">
|
|
55
|
+
<mat-slide-toggle
|
|
56
|
+
[checked]="showSeconds()"
|
|
57
|
+
(change)="showSeconds.set($event.checked)">
|
|
58
|
+
Show Seconds
|
|
59
|
+
</mat-slide-toggle>
|
|
60
|
+
<span class="toggle-description">
|
|
61
|
+
@if (mode() === 'digital') {
|
|
62
|
+
Display seconds in the time
|
|
63
|
+
} @else {
|
|
64
|
+
Show the second hand on the clock
|
|
65
|
+
}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- Background Toggle -->
|
|
70
|
+
<div class="toggle-section">
|
|
71
|
+
<mat-slide-toggle
|
|
72
|
+
[checked]="hasBackground()"
|
|
73
|
+
(change)="hasBackground.set($event.checked)">
|
|
74
|
+
Background
|
|
75
|
+
</mat-slide-toggle>
|
|
76
|
+
<span class="toggle-description"
|
|
77
|
+
>Adds a background behind the clock</span
|
|
78
|
+
>
|
|
79
|
+
</div>
|
|
80
|
+
</mat-dialog-content>
|
|
81
|
+
|
|
82
|
+
<mat-dialog-actions align="end">
|
|
83
|
+
<button mat-button (click)="onCancel()">Cancel</button>
|
|
84
|
+
<button mat-flat-button (click)="save()" [disabled]="!hasChanged()">
|
|
85
|
+
Save
|
|
86
|
+
</button>
|
|
87
|
+
</mat-dialog-actions>
|
|
88
|
+
`,
|
|
89
|
+
styles: [
|
|
90
|
+
`
|
|
91
|
+
mat-dialog-content {
|
|
92
|
+
display: block;
|
|
93
|
+
overflow-y: auto;
|
|
94
|
+
overflow-x: hidden;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.mode-selection,
|
|
98
|
+
.format-selection {
|
|
99
|
+
margin-top: 1rem;
|
|
100
|
+
margin-bottom: 2rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.section-label {
|
|
104
|
+
display: block;
|
|
105
|
+
margin-bottom: 0.75rem;
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
mat-radio-group {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
gap: 0.75rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
mat-radio-button {
|
|
116
|
+
margin: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Toggle section */
|
|
120
|
+
.toggle-section {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
gap: 0.75rem;
|
|
124
|
+
margin-bottom: 0.5rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.toggle-description {
|
|
128
|
+
margin: 0;
|
|
129
|
+
}
|
|
130
|
+
`,
|
|
131
|
+
],
|
|
132
|
+
})
|
|
133
|
+
export class ClockStateDialogComponent {
|
|
134
|
+
private readonly data = inject<ClockWidgetState>(MAT_DIALOG_DATA);
|
|
135
|
+
private readonly dialogRef = inject(MatDialogRef<ClockStateDialogComponent>);
|
|
136
|
+
|
|
137
|
+
// State signals
|
|
138
|
+
readonly mode = signal<'analog' | 'digital'>(this.data.mode ?? 'digital');
|
|
139
|
+
readonly hasBackground = signal<boolean>(this.data.hasBackground ?? true);
|
|
140
|
+
readonly timeFormat = signal<'12h' | '24h'>(this.data.timeFormat ?? '24h');
|
|
141
|
+
readonly showSeconds = signal<boolean>(this.data.showSeconds ?? true);
|
|
142
|
+
|
|
143
|
+
// Store original values for comparison
|
|
144
|
+
private readonly originalMode = this.data.mode ?? 'digital';
|
|
145
|
+
private readonly originalHasBackground = this.data.hasBackground ?? true;
|
|
146
|
+
private readonly originalTimeFormat = this.data.timeFormat ?? '24h';
|
|
147
|
+
private readonly originalShowSeconds = this.data.showSeconds ?? true;
|
|
148
|
+
|
|
149
|
+
// Computed values
|
|
150
|
+
readonly hasChanged = computed(
|
|
151
|
+
() =>
|
|
152
|
+
this.mode() !== this.originalMode ||
|
|
153
|
+
this.hasBackground() !== this.originalHasBackground ||
|
|
154
|
+
this.timeFormat() !== this.originalTimeFormat ||
|
|
155
|
+
this.showSeconds() !== this.originalShowSeconds
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
onCancel(): void {
|
|
159
|
+
this.dialogRef.close();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
save(): void {
|
|
163
|
+
this.dialogRef.close({
|
|
164
|
+
mode: this.mode(),
|
|
165
|
+
hasBackground: this.hasBackground(),
|
|
166
|
+
timeFormat: this.timeFormat(),
|
|
167
|
+
showSeconds: this.showSeconds(),
|
|
168
|
+
} as ClockWidgetState);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@if (isDigital) {
|
|
2
|
+
<ngx-digital-clock
|
|
3
|
+
[timeFormat]="state().timeFormat || '24h'"
|
|
4
|
+
[showSeconds]="state().showSeconds ?? true"
|
|
5
|
+
[hasBackground]="state().hasBackground ?? false"
|
|
6
|
+
/>
|
|
7
|
+
} @else if (isAnalog) {
|
|
8
|
+
<ngx-analog-clock
|
|
9
|
+
[hasBackground]="state().hasBackground ?? false"
|
|
10
|
+
[showSeconds]="state().showSeconds ?? true"
|
|
11
|
+
/>
|
|
12
|
+
} @else {
|
|
13
|
+
<div class="svg-wrapper" [class.has-background]="state().hasBackground">
|
|
14
|
+
<div class="svg-placeholder" [innerHTML]="safeSvgIcon"></div>
|
|
15
|
+
</div>
|
|
16
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// clock-widget.component.scss
|
|
2
|
+
:host {
|
|
3
|
+
display: block;
|
|
4
|
+
container-type: size;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Shared styles for all widget states
|
|
11
|
+
%widget-base {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
height: 100%;
|
|
16
|
+
width: 100%;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
transition: background-color var(--mat-sys-motion-duration-medium2)
|
|
19
|
+
var(--mat-sys-motion-easing-standard);
|
|
20
|
+
|
|
21
|
+
&.has-background {
|
|
22
|
+
background-color: var(--mat-sys-surface-container-high);
|
|
23
|
+
border-radius: 4px;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Clock widget styles
|
|
28
|
+
.clock-widget {
|
|
29
|
+
@extend %widget-base;
|
|
30
|
+
padding: var(--mat-sys-spacing-4);
|
|
31
|
+
color: var(--mat-sys-on-surface-variant, #6c757d);
|
|
32
|
+
|
|
33
|
+
&.has-background {
|
|
34
|
+
color: var(--mat-sys-on-surface, #1f1f1f);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:hover {
|
|
38
|
+
opacity: 0.8;
|
|
39
|
+
color: var(--mat-sys-primary, #6750a4);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Digital clock styles moved to digital-clock.component.scss
|
|
44
|
+
|
|
45
|
+
// Analog clock styles
|
|
46
|
+
.analog-clock {
|
|
47
|
+
width: min(80cqw, 80cqh);
|
|
48
|
+
aspect-ratio: 1 / 1;
|
|
49
|
+
position: relative;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.clock-face {
|
|
53
|
+
width: 100%;
|
|
54
|
+
height: 100%;
|
|
55
|
+
border: 2px solid currentColor;
|
|
56
|
+
border-radius: 50%;
|
|
57
|
+
position: relative;
|
|
58
|
+
|
|
59
|
+
&::before,
|
|
60
|
+
&::after {
|
|
61
|
+
content: "";
|
|
62
|
+
position: absolute;
|
|
63
|
+
background-color: currentColor;
|
|
64
|
+
left: 50%;
|
|
65
|
+
transform: translateX(-50%);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 12 o'clock marker
|
|
69
|
+
&::before {
|
|
70
|
+
width: 2px;
|
|
71
|
+
height: 10%;
|
|
72
|
+
top: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 6 o'clock marker
|
|
76
|
+
&::after {
|
|
77
|
+
width: 2px;
|
|
78
|
+
height: 10%;
|
|
79
|
+
bottom: 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.hour-hand,
|
|
84
|
+
.minute-hand {
|
|
85
|
+
position: absolute;
|
|
86
|
+
background-color: currentColor;
|
|
87
|
+
left: 50%;
|
|
88
|
+
bottom: 50%;
|
|
89
|
+
transform-origin: 50% 100%;
|
|
90
|
+
border-radius: 2px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.hour-hand {
|
|
94
|
+
width: 4px;
|
|
95
|
+
height: 25%;
|
|
96
|
+
transform: translateX(-50%) rotate(30deg); // 1 o'clock position
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.minute-hand {
|
|
100
|
+
width: 2px;
|
|
101
|
+
height: 35%;
|
|
102
|
+
transform: translateX(-50%) rotate(90deg); // 15 minutes position
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.center-dot {
|
|
106
|
+
position: absolute;
|
|
107
|
+
width: 8px;
|
|
108
|
+
height: 8px;
|
|
109
|
+
background-color: currentColor;
|
|
110
|
+
border-radius: 50%;
|
|
111
|
+
top: 50%;
|
|
112
|
+
left: 50%;
|
|
113
|
+
transform: translate(-50%, -50%);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// SVG placeholder styles
|
|
117
|
+
.svg-wrapper {
|
|
118
|
+
@extend %widget-base;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.svg-placeholder {
|
|
123
|
+
width: min(80cqw, 80cqh);
|
|
124
|
+
aspect-ratio: 1 / 1;
|
|
125
|
+
opacity: 0.3;
|
|
126
|
+
transition: transform 0.3s ease-in-out, opacity 0.3s ease;
|
|
127
|
+
transform-origin: center center;
|
|
128
|
+
|
|
129
|
+
::ng-deep svg {
|
|
130
|
+
width: 100%;
|
|
131
|
+
height: 100%;
|
|
132
|
+
display: block;
|
|
133
|
+
|
|
134
|
+
// Hour markers and tick marks
|
|
135
|
+
.clock-face {
|
|
136
|
+
stroke: var(--mat-sys-on-surface, #1d1b20);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Clock hands
|
|
140
|
+
.clock-hour-hand {
|
|
141
|
+
fill: var(--mat-sys-on-surface, #1d1b20);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.clock-minute-hand {
|
|
145
|
+
fill: var(--mat-sys-on-surface, #1d1b20);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.clock-second-hand {
|
|
149
|
+
fill: var(--mat-sys-primary, #6750a4);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.has-background & ::ng-deep svg {
|
|
154
|
+
// Background circle (matching live analog clock behavior)
|
|
155
|
+
circle {
|
|
156
|
+
fill: var(--mat-sys-surface, #fffbfe);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// clock-widget.component.ts
|
|
2
|
+
import { Component, inject, signal } from '@angular/core';
|
|
3
|
+
import { Widget, WidgetMetadata } from '@dragonworks/ngx-dashboard';
|
|
4
|
+
import { svgIcon } from './clock-widget.metadata';
|
|
5
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
6
|
+
import { ClockStateDialogComponent } from './clock-state-dialog.component';
|
|
7
|
+
import { MatDialog } from '@angular/material/dialog';
|
|
8
|
+
import { DigitalClockComponent } from './digital-clock/digital-clock.component';
|
|
9
|
+
import { AnalogClockComponent } from './analog-clock/analog-clock.component';
|
|
10
|
+
|
|
11
|
+
export interface ClockWidgetState {
|
|
12
|
+
mode: 'analog' | 'digital';
|
|
13
|
+
hasBackground?: boolean;
|
|
14
|
+
timeFormat?: '12h' | '24h';
|
|
15
|
+
showSeconds?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@Component({
|
|
19
|
+
selector: 'ngx-dashboard-clock-widget',
|
|
20
|
+
standalone: true,
|
|
21
|
+
imports: [DigitalClockComponent, AnalogClockComponent],
|
|
22
|
+
templateUrl: './clock-widget.component.html',
|
|
23
|
+
styleUrl: './clock-widget.component.scss',
|
|
24
|
+
})
|
|
25
|
+
export class ClockWidgetComponent implements Widget {
|
|
26
|
+
static metadata: WidgetMetadata = {
|
|
27
|
+
widgetTypeid: '@default/clock-widget',
|
|
28
|
+
name: 'Clock',
|
|
29
|
+
description: 'Display time in analog or digital format',
|
|
30
|
+
svgIcon,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
readonly #sanitizer = inject(DomSanitizer);
|
|
34
|
+
readonly #dialog = inject(MatDialog);
|
|
35
|
+
|
|
36
|
+
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon);
|
|
37
|
+
|
|
38
|
+
state = signal<ClockWidgetState>({
|
|
39
|
+
mode: 'analog',
|
|
40
|
+
hasBackground: true,
|
|
41
|
+
timeFormat: '24h',
|
|
42
|
+
showSeconds: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
// No timer logic needed - DigitalClock manages its own time
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
dashboardSetState(state?: unknown) {
|
|
50
|
+
if (state) {
|
|
51
|
+
this.state.update((current) => ({
|
|
52
|
+
...current,
|
|
53
|
+
...(state as ClockWidgetState),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
dashboardGetState(): ClockWidgetState {
|
|
59
|
+
return { ...this.state() };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
dashboardEditState(): void {
|
|
63
|
+
const dialogRef = this.#dialog.open(ClockStateDialogComponent, {
|
|
64
|
+
data: this.dashboardGetState(),
|
|
65
|
+
width: '400px',
|
|
66
|
+
maxWidth: '90vw',
|
|
67
|
+
disableClose: false,
|
|
68
|
+
autoFocus: false,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
dialogRef
|
|
72
|
+
.afterClosed()
|
|
73
|
+
.subscribe((result: ClockWidgetState | undefined) => {
|
|
74
|
+
if (result) {
|
|
75
|
+
this.state.set(result);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get isAnalog(): boolean {
|
|
81
|
+
return this.state().mode === 'analog';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get isDigital(): boolean {
|
|
85
|
+
return this.state().mode === 'digital';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const svgIcon = `
|
|
2
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" preserveAspectRatio="xMidYMid meet">
|
|
3
|
+
<use transform="matrix(-1,0,0,1,800,0)" href="#one-half" />
|
|
4
|
+
<g id="one-half">
|
|
5
|
+
<g id="one-fourth">
|
|
6
|
+
<path d="m400 40v107" stroke-width="26.7" stroke="currentColor" />
|
|
7
|
+
<g id="one-twelfth">
|
|
8
|
+
<path
|
|
9
|
+
d="m580 88.233-42.5 73.612"
|
|
10
|
+
stroke-width="26.7"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
/>
|
|
13
|
+
<g id="one-thirtieth">
|
|
14
|
+
<path
|
|
15
|
+
id="one-sixtieth"
|
|
16
|
+
d="m437.63 41.974-3.6585 34.808"
|
|
17
|
+
stroke-width="13.6"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
/>
|
|
20
|
+
<use transform="rotate(6 400 400)" href="#one-sixtieth" />
|
|
21
|
+
</g>
|
|
22
|
+
<use transform="rotate(12 400 400)" href="#one-thirtieth" />
|
|
23
|
+
</g>
|
|
24
|
+
<use transform="rotate(30 400 400)" href="#one-twelfth" />
|
|
25
|
+
<use transform="rotate(60 400 400)" href="#one-twelfth" />
|
|
26
|
+
</g>
|
|
27
|
+
<use transform="rotate(90 400 400)" href="#one-fourth" />
|
|
28
|
+
</g>
|
|
29
|
+
<path
|
|
30
|
+
class="clock-hour-hand"
|
|
31
|
+
id="anim-clock-hour-hand"
|
|
32
|
+
d="m 381.925,476 h 36.15 l 5e-4,-300.03008 L 400,156.25 381.9245,175.96992 Z"
|
|
33
|
+
transform="rotate(110.2650694444, 400, 400)"
|
|
34
|
+
/>
|
|
35
|
+
<path
|
|
36
|
+
class="clock-minute-hand"
|
|
37
|
+
id="anim-clock-minute-hand"
|
|
38
|
+
d="M 412.063,496.87456 H 387.937 L 385.249,65.68306 400,52.75 414.751,65.68306 Z"
|
|
39
|
+
transform="rotate(243.1808333333, 400, 400)"
|
|
40
|
+
/>
|
|
41
|
+
</svg>
|
|
42
|
+
`;
|