Skip to content

Commit 42282ca

Browse files
Hypferelraro
authored andcommitted
feat(timers): Introduce custom labels for timers
1 parent ddf825f commit 42282ca

6 files changed

Lines changed: 115 additions & 29 deletions

File tree

backend/lib/doc/Configuration.openapi.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,11 @@
393393
"enabled": {
394394
"type": "boolean"
395395
},
396+
"label": {
397+
"type": "string",
398+
"pattern": "^.{0,24}$",
399+
"description": "An optional user-defined label for the timer"
400+
},
396401
"dow": {
397402
"type": "array",
398403
"description": "Day of Week\nSunday = 0, Monday = 1, ... Saturday = 6",

backend/lib/entities/core/ValetudoTimer.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class ValetudoTimer extends SerializableEntity {
99
* @param {object} options
1010
* @param {string} [options.id] uuidv4
1111
* @param {boolean} options.enabled
12+
* @param {string} [options.label]
1213
*
1314
* @param {Array<number>} options.dow Sunday = 0 because js
1415
* @param {number} options.hour 0-23
@@ -31,6 +32,12 @@ class ValetudoTimer extends SerializableEntity {
3132

3233
this.id = options.id ?? uuid.v4();
3334
this.enabled = options.enabled;
35+
36+
if (typeof options.label === "string" && options.label.length > 0) {
37+
this.label = options.label.substring(0, 24);
38+
}
39+
40+
3441
this.dow = [...options.dow].sort((a,b) => {
3542
return a - b;
3643
});

backend/lib/webserver/TimerRouter.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class TimerRouter {
9090
} else {
9191
const storedTimers = this.config.get("timers");
9292
const newTimer = new ValetudoTimer({
93+
label: req.body.label,
9394
enabled: req.body.enabled === true,
9495
dow: req.body.dow,
9596
hour: req.body.hour,
@@ -128,6 +129,7 @@ class TimerRouter {
128129
} else {
129130
const newTimer = new ValetudoTimer({
130131
id: req.params.id,
132+
label: req.body.label,
131133
enabled: req.body.enabled === true,
132134
dow: req.body.dow,
133135
hour: req.body.hour,

frontend/src/api/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ export enum ValetudoTimerPreActionType {
193193
export interface Timer {
194194
id: string;
195195
enabled: boolean;
196+
label?: string;
196197
dow: Array<number>;
197198
hour: number;
198199
minute: number;

frontend/src/valetudo/timers/TimerCard.tsx

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,35 @@ const TimerCard: FunctionComponent<TimerCardProps> = ({
8080
}, [timer]);
8181

8282
const weekdayLabels = React.useMemo(() => {
83-
return weekdays.map((day, i) => {
84-
const enabled = timerInLocalTime.dow.includes(day.dow);
83+
return (
84+
<Grid
85+
container
86+
direction="row"
87+
sx={{
88+
justifyContent: "space-between"
89+
}}
90+
>
91+
{weekdays.map((day, i) => {
92+
const enabled = timerInLocalTime.dow.includes(day.dow);
8593

86-
return (
87-
<Typography
88-
key={day.label}
89-
variant={"body2"}
90-
color={enabled ? "textPrimary" : "textSecondary"}
91-
component={"span"}
92-
sx={i < weekdays.length - 1 ? { marginRight: 1 } : {}}
93-
>
94-
{day.label.toUpperCase().slice(0, 3)}
95-
</Typography>
96-
);
97-
});
94+
return (
95+
<Grid
96+
item
97+
key={day.label}
98+
>
99+
<Typography
100+
variant={"body2"}
101+
color={enabled ? "textPrimary" : "textSecondary"}
102+
component={"span"}
103+
sx={i < weekdays.length - 1 ? { marginRight: 1 } : {}}
104+
>
105+
{day.label.toUpperCase().slice(0, 3)}
106+
</Typography>
107+
</Grid>
108+
);
109+
})}
110+
</Grid>
111+
);
98112
}, [timerInLocalTime]);
99113

100114
const timeLabel = React.useMemo(() => {
@@ -126,6 +140,29 @@ const TimerCard: FunctionComponent<TimerCardProps> = ({
126140
return <Typography variant={"subtitle1"}>{label}</Typography>;
127141
}, [timer]);
128142

143+
const preActionLabel = React.useMemo(() => {
144+
if (timer?.pre_actions?.length) {
145+
return <Typography variant={"subtitle1"}>
146+
{`${timer.pre_actions.length} Pre-Action${timer.pre_actions.length > 1 ? "s" : ""}`}
147+
</Typography>;
148+
} else {
149+
return null;
150+
}
151+
}, [timer]);
152+
153+
const timerLabel = React.useMemo(() => {
154+
return timer?.label || "Timer";
155+
}, [timer]);
156+
157+
const dialogTimerText = React.useMemo(() => {
158+
if (timer?.label) {
159+
return `"${timer.label}"`;
160+
} else {
161+
return "this timer";
162+
}
163+
164+
}, [timer]);
165+
129166
return (
130167
<Card
131168
key={timer.id}
@@ -134,9 +171,9 @@ const TimerCard: FunctionComponent<TimerCardProps> = ({
134171
<CardContent>
135172
<Grid
136173
container
137-
spacing={4}
138174
alignItems="center"
139175
justifyContent="space-between"
176+
sx={{minWidth: "16rem"}}
140177
>
141178
<Grid item>
142179
<FormControlLabel
@@ -151,10 +188,10 @@ const TimerCard: FunctionComponent<TimerCardProps> = ({
151188
<Typography
152189
variant="h6"
153190
gutterBottom
154-
sx={{ marginBottom: 0 }}
191+
sx={{ marginBottom: 0, userSelect: "none" }}
155192
title={timer.id}
156193
>
157-
Timer
194+
{timerLabel}
158195
</Typography>
159196
}
160197
/>
@@ -196,6 +233,8 @@ const TimerCard: FunctionComponent<TimerCardProps> = ({
196233

197234
{actionLabel}
198235

236+
{preActionLabel}
237+
199238
<Dialog
200239
open={deleteDialogOpen}
201240
onClose={() => {
@@ -205,7 +244,7 @@ const TimerCard: FunctionComponent<TimerCardProps> = ({
205244
<DialogTitle>Delete timer?</DialogTitle>
206245
<DialogContent>
207246
<DialogContentText>
208-
Do you really want to delete this timer?
247+
Do you really want to delete {dialogTimerText}?
209248
</DialogContentText>
210249
</DialogContent>
211250
<DialogActions>
@@ -250,7 +289,7 @@ const TimerCard: FunctionComponent<TimerCardProps> = ({
250289
<DialogTitle>Execute timer?</DialogTitle>
251290
<DialogContent>
252291
<DialogContentText>
253-
Do you really want to execute this timer right now?
292+
Do you really want to execute {dialogTimerText} right now?
254293
</DialogContentText>
255294
</DialogContent>
256295
<DialogActions>

frontend/src/valetudo/timers/TimerEditDialog.tsx

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Divider,
1010
FormControl,
1111
FormControlLabel,
12+
Grid,
1213
InputLabel,
1314
MenuItem,
1415
Select,
@@ -196,20 +197,51 @@ const TimerEditDialog: FunctionComponent<TimerDialogProps> = ({
196197
</DialogTitle>
197198
<DialogContent>
198199
<Divider textAlign="left" sx={{mb: 1}}>General</Divider>
199-
<FormControlLabel
200-
sx={{padding: "0.5rem"}}
201-
control={
202-
<Checkbox
203-
checked={editTimer.enabled}
204-
onChange={(e) => {
200+
<Grid
201+
container
202+
direction="column"
203+
>
204+
<Grid
205+
item
206+
sx={{paddingLeft: "0.5rem"}}
207+
>
208+
<FormControlLabel
209+
control={
210+
<Checkbox
211+
checked={editTimer.enabled}
212+
onChange={(e) => {
213+
const newTimer = deepCopy(editTimer);
214+
newTimer.enabled = e.target.checked;
215+
setEditTimer(newTimer);
216+
}}
217+
/>
218+
}
219+
label="Enabled"
220+
/>
221+
</Grid>
222+
223+
<Grid
224+
item
225+
sx={{paddingLeft: "0.5rem", marginTop: "0.5rem"}}
226+
>
227+
<TextField
228+
label="Custom Label"
229+
value={editTimer.label}
230+
variant="standard"
231+
onChange={e => {
205232
const newTimer = deepCopy(editTimer);
206-
newTimer.enabled = e.target.checked;
233+
newTimer.label = e.target.value.substring(0, 24);
234+
235+
if (newTimer.label.length === 0) {
236+
newTimer.label = undefined;
237+
}
238+
207239
setEditTimer(newTimer);
208240
}}
209241
/>
210-
}
211-
label="Enabled"
212-
/>
242+
</Grid>
243+
</Grid>
244+
213245

214246
<Divider textAlign="left" sx={{mt: 1, mb: 1.5}}>Schedule</Divider>
215247

0 commit comments

Comments
 (0)