@iobroker/adapter-react-v5 7.1.0 → 7.1.3

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.
@@ -1,544 +1,561 @@
1
- import React, { Component } from 'react';
2
-
3
- import { Checkbox, Button, MenuItem, Select, FormControlLabel, AppBar, Tabs, Tab, TextField } from '@mui/material';
4
-
5
- import I18n from '../i18n';
6
- import convertCronToText from './SimpleCron/cronText';
7
-
8
- const styles: Record<string, React.CSSProperties> = {
9
- mainDiv: {
10
- width: '100%',
11
- height: '100%',
12
- },
13
- periodSelect: {
14
- // margin: '0 10px 60px 10px',
15
- display: 'block',
16
- width: 200,
17
- },
18
- slider: {
19
- marginTop: 20,
20
- display: 'block',
21
- width: '100%',
22
- },
23
- tabContent: {
24
- padding: 20,
25
- height: 'calc(100% - 240px)',
26
- overflow: 'auto',
27
- },
28
- numberButton: {
29
- padding: 4,
30
- minWidth: 40,
31
- margin: 5,
32
- },
33
- numberButtonBreak: {
34
- display: 'block',
35
- },
36
- appBar: {
37
- color: 'white',
38
- },
39
- };
40
-
41
- const WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
42
- const MONTHS = [
43
- 'January',
44
- 'February',
45
- 'March',
46
- 'April',
47
- 'May',
48
- 'June',
49
- 'July',
50
- 'August',
51
- 'September',
52
- 'October',
53
- 'November',
54
- 'December',
55
- ];
56
-
57
- // 5-7,9-11 => [5,6,7,9,10,11]
58
- function convertMinusIntoArray(value: string | false | undefined, max: number): number[] {
59
- const result: number[] = [];
60
-
61
- if (value === '*') {
62
- if (max === 24 || max === 60 || max === 7) {
63
- for (let i = 0; i < max; i++) {
64
- result.push(i);
65
- }
66
- } else {
67
- for (let i = 1; i <= max; i++) {
68
- result.push(i);
69
- }
70
- }
71
- return result; // array with entries max
72
- }
73
-
74
- const parts = (value || '').toString().split(',');
75
-
76
- for (let p = 0; p < parts.length; p++) {
77
- if (!parts[p].trim().length) {
78
- continue;
79
- }
80
- const items = parts[p].trim().split('-');
81
- if (items.length > 1) {
82
- const iMax = parseInt(items[1], 10);
83
- for (let i = parseInt(items[0], 10); i <= iMax; i++) {
84
- result.push(i);
85
- }
86
- } else {
87
- result.push(parseInt(parts[p], 10));
88
- }
89
- }
90
-
91
- result.sort();
92
-
93
- // remove double entries
94
- for (let p = result.length - 1; p >= 0; p--) {
95
- if (result[p] === result[p + 1]) {
96
- result.splice(p + 1, 1);
97
- }
98
- }
99
-
100
- return result;
101
- }
102
-
103
- // [5,6,7,9,10,11] => 5-7,9-11
104
- function convertArrayIntoMinus(value: number | number[], max: number): string {
105
- if (typeof value !== 'object') {
106
- value = [value];
107
- }
108
- if (value.length === max) {
109
- return '*';
110
- }
111
- const newParts = [];
112
- if (!value.length) {
113
- return '-';
114
- }
115
- value = value.map(a => parseInt(a as any as string, 10));
116
-
117
- value.sort((a, b) => a - b);
118
-
119
- let start = value[0];
120
- let end = value[0];
121
- for (let p = 1; p < value.length; p++) {
122
- if (value[p] - 1 !== parseInt(value[p - 1] as any as string, 10)) {
123
- if (start === end) {
124
- newParts.push(start);
125
- } else if (end - 1 === start) {
126
- newParts.push(`${start},${end}`);
127
- } else {
128
- newParts.push(`${start}-${end}`);
129
- }
130
- start = value[p];
131
- }
132
- end = value[p];
133
- }
134
-
135
- if (start === end) {
136
- newParts.push(start);
137
- } else if (end - 1 === start) {
138
- newParts.push(`${start},${end}`);
139
- } else {
140
- newParts.push(`${start}-${end}`);
141
- }
142
-
143
- return newParts.join(',');
144
- }
145
-
146
- type CronNames = 'seconds' | 'minutes' | 'hours' | 'dates' | 'months' | 'dow';
147
-
148
- interface CronProps {
149
- seconds: string | false | null;
150
- minutes: string | null;
151
- hours: string | null;
152
- dates: string | null;
153
- months: string | null;
154
- dow: string | null;
155
- }
156
-
157
- interface ComplexCronProps {
158
- cronExpression: string;
159
- onChange: (cron: string) => void;
160
- language: ioBroker.Languages;
161
- }
162
-
163
- // type CronModes = 'every' | 'everyN' | 'specific';
164
-
165
- interface ComplexCronState {
166
- extended: boolean;
167
- tab: number;
168
- cron: string;
169
- seconds?: string | false;
170
- minutes?: string;
171
- hours?: string;
172
- dates?: string;
173
- months?: string;
174
- dow?: string;
175
- modes: CronProps;
176
- }
177
-
178
- class ComplexCron extends Component<ComplexCronProps, ComplexCronState> {
179
- constructor(props: ComplexCronProps) {
180
- super(props);
181
- let cron =
182
- typeof this.props.cronExpression === 'string'
183
- ? this.props.cronExpression.replace(/^["']/, '').replace(/["']\n?$/, '')
184
- : '';
185
- if (cron[0] === '{') {
186
- cron = '';
187
- }
188
- const state = ComplexCron.cron2state(cron || '* * * * *');
189
-
190
- this.state = {
191
- extended: false,
192
- tab: state.seconds !== false ? 1 : 0,
193
- cron: ComplexCron.state2cron(state),
194
- modes: {
195
- seconds: null,
196
- minutes: null,
197
- hours: null,
198
- dates: null,
199
- months: null,
200
- dow: null,
201
- },
202
- };
203
- Object.assign(this.state, state);
204
- if (this.state.cron !== this.props.cronExpression) {
205
- setTimeout(() => this.props.onChange && this.props.onChange(this.state.cron), 100);
206
- }
207
- }
208
-
209
- static cron2state(cron: string): CronProps {
210
- cron = cron.replace(/['"]/g, '').trim();
211
- const cronParts = cron.split(' ').map(p => p.trim());
212
- let options: CronProps;
213
-
214
- if (cronParts.length === 6) {
215
- options = {
216
- seconds: cronParts[0] || '*',
217
- minutes: cronParts[1] || '*',
218
- hours: cronParts[2] || '*',
219
- dates: cronParts[3] || '*',
220
- months: cronParts[4] || '*',
221
- dow: cronParts[5] || '*',
222
- };
223
- } else {
224
- options = {
225
- seconds: false,
226
- minutes: cronParts[0] || '*',
227
- hours: cronParts[1] || '*',
228
- dates: cronParts[2] || '*',
229
- months: cronParts[3] || '*',
230
- dow: cronParts[4] || '*',
231
- };
232
- }
233
- return options;
234
- }
235
-
236
- static state2cron(state: ComplexCronState | CronProps): string {
237
- let text = `${state.minutes} ${state.hours} ${state.dates} ${state.months} ${state.dow}`;
238
- if (state.seconds !== false) {
239
- text = `${state.seconds} ${text}`;
240
- }
241
- return text;
242
- }
243
-
244
- recalcCron(): void {
245
- const cron = ComplexCron.state2cron(this.state);
246
- if (cron !== this.state.cron) {
247
- this.setState({ cron }, () => this.props.onChange && this.props.onChange(this.state.cron));
248
- }
249
- }
250
-
251
- onToggle(i: boolean | number, type: CronNames, max: number): void {
252
- if (i === true) {
253
- this.setCronAttr(type, '*');
254
- } else if (i === false) {
255
- if (max === 60 || max === 24) {
256
- this.setCronAttr(type, '0');
257
- } else {
258
- this.setCronAttr(type, '1');
259
- }
260
- } else {
261
- const nums = convertMinusIntoArray(this.state[type], max);
262
- const pos = nums.indexOf(i);
263
- if (pos !== -1) {
264
- nums.splice(pos, 1);
265
- } else {
266
- nums.push(i);
267
- nums.sort();
268
- }
269
- this.setCronAttr(type, convertArrayIntoMinus(nums, max));
270
- }
271
- }
272
-
273
- getDigitsSelector(type: CronNames, max: number): React.JSX.Element[] {
274
- let values = [];
275
- if (max === 7) {
276
- values = [1, 2, 3, 4, 5, 6, 0];
277
- } else if (max === 60 || max === 24) {
278
- for (let i = 0; i < max; i++) {
279
- values.push(i);
280
- }
281
- } else {
282
- for (let i = 1; i <= max; i++) {
283
- values.push(i);
284
- }
285
- }
286
-
287
- const parts = convertMinusIntoArray(this.state[type], max);
288
-
289
- return [
290
- <Button
291
- key="removeall"
292
- variant="outlined"
293
- style={styles.numberButton}
294
- // style={{paddingBottom: 20}}
295
- color="primary"
296
- onClick={() => this.onToggle(false, type, max)}
297
- >
298
- {I18n.t('ra_Deselect all')}
299
- </Button>,
300
- <Button
301
- key="addall"
302
- variant="contained"
303
- // style={{paddingBottom: 20}}
304
- style={styles.numberButton}
305
- color="secondary"
306
- onClick={() => this.onToggle(true, type, max)}
307
- >
308
- {I18n.t('ra_Select all')}
309
- </Button>,
310
- <div key="all">
311
- {values.map(i => [
312
- (max === 7 && i === 4) ||
313
- (max === 12 && i === 7) ||
314
- (max === 31 && !((i - 1) % 10)) ||
315
- (max === 60 && i && !(i % 10)) ||
316
- (max === 24 && i && !(i % 6)) ? (
317
- <div
318
- key={`allInner${i}`}
319
- style={{ width: '100%' }}
320
- />
321
- ) : null,
322
- <Button
323
- key={`_${i}`}
324
- variant={parts.indexOf(i) !== -1 ? 'contained' : 'outlined'}
325
- style={styles.numberButton}
326
- color={parts.indexOf(i) !== -1 ? 'secondary' : 'primary'}
327
- onClick={() => this.onToggle(i, type, max)}
328
- >
329
- {max === 7 ? I18n.t(WEEKDAYS[i]) : max === 12 ? MONTHS[i - 1] : i}
330
- </Button>,
331
- ])}
332
- </div>,
333
- ];
334
- }
335
-
336
- getPeriodsTab(type: CronNames, max: number): React.JSX.Element | null {
337
- const value = this.state[type];
338
- let every = value === '*';
339
- let everyN = value === undefined || value === null ? false : value.toString().includes('/');
340
- let select;
341
- if (this.state.modes[type] === null) {
342
- select = every ? 'every' : everyN ? 'everyN' : 'specific';
343
- const modes = JSON.parse(JSON.stringify(this.state.modes));
344
- modes[type] = select;
345
- setTimeout(() => this.setState({ modes }, () => this.recalcCron()), 100);
346
- return null;
347
- }
348
-
349
- every = this.state.modes[type] === 'every';
350
- everyN = this.state.modes[type] === 'everyN';
351
- select = this.state.modes[type];
352
-
353
- let valueNumber = 1;
354
- if (everyN && value) {
355
- valueNumber = parseInt(value.replace('*/', ''), 10) || 1;
356
- }
357
-
358
- return (
359
- <div>
360
- <Select
361
- variant="standard"
362
- style={{ ...styles.periodSelect, verticalAlign: 'bottom' }}
363
- value={select}
364
- onChange={e => {
365
- const modes = JSON.parse(JSON.stringify(this.state.modes));
366
- modes[type] = e.target.value;
367
- if (e.target.value === 'every') {
368
- this.setCronAttr(type, '*', modes);
369
- } else if (e.target.value === 'everyN') {
370
- const num = parseInt((this.state[type] || '').toString().replace('*/', ''), 10) || 1;
371
- this.setCronAttr(type, `*/${num}`, modes);
372
- } else if (e.target.value === 'specific') {
373
- let num = parseInt((this.state[type] || '').toString().split(',')[0], 10) || 0;
374
- if (!num && (type === 'months' || type === 'dates')) {
375
- num = 1;
376
- }
377
- this.setCronAttr(type, convertArrayIntoMinus(num, max), modes);
378
- }
379
- }}
380
- >
381
- <MenuItem
382
- key="every"
383
- value="every"
384
- >
385
- {I18n.t(`sc_every_${type}`)}
386
- </MenuItem>
387
- <MenuItem
388
- key="everyN"
389
- value="everyN"
390
- >
391
- {I18n.t(`sc_everyN_${type}`)}
392
- </MenuItem>
393
- <MenuItem
394
- key="specific"
395
- value="specific"
396
- >
397
- {I18n.t(`sc_specific_${type}`)}
398
- </MenuItem>
399
- </Select>
400
- {/* everyN && false && <span>{value}</span> */}
401
- {everyN && (
402
- <TextField
403
- variant="standard"
404
- key="interval"
405
- label={I18n.t(`sc_${type}`)}
406
- value={valueNumber}
407
- slotProps={{
408
- htmlInput: {
409
- min: 1,
410
- max,
411
- },
412
- inputLabel: {
413
- shrink: true,
414
- },
415
- }}
416
- onChange={e => {
417
- // @ts-expect-error is allowed
418
- this.setState({ [type]: `*/${e.target.value}` }, () => this.recalcCron());
419
- }}
420
- type="number"
421
- margin="normal"
422
- />
423
- )}
424
- {!every && !everyN && this.getDigitsSelector(type, max)}
425
- </div>
426
- );
427
- }
428
-
429
- static convertCronToText(cron: string, lang: ioBroker.Languages): string {
430
- if (cron.split(' ').includes('-')) {
431
- return I18n.t('ra_Invalid CRON');
432
- }
433
- return convertCronToText(cron, lang);
434
- }
435
-
436
- setCronAttr(attr: CronNames, value: string, modes?: CronProps): void {
437
- if (modes) {
438
- if (attr === 'seconds') {
439
- this.setState({ seconds: value, modes }, () => this.recalcCron());
440
- } else if (attr === 'minutes') {
441
- this.setState({ minutes: value, modes }, () => this.recalcCron());
442
- } else if (attr === 'hours') {
443
- this.setState({ hours: value, modes }, () => this.recalcCron());
444
- } else if (attr === 'dates') {
445
- this.setState({ dates: value, modes }, () => this.recalcCron());
446
- } else if (attr === 'months') {
447
- this.setState({ months: value, modes }, () => this.recalcCron());
448
- } else if (attr === 'dow') {
449
- this.setState({ dow: value, modes }, () => this.recalcCron());
450
- } else {
451
- this.setState({ modes }, () => this.recalcCron());
452
- }
453
- } else if (attr === 'seconds') {
454
- this.setState({ seconds: value }, () => this.recalcCron());
455
- } else if (attr === 'minutes') {
456
- this.setState({ minutes: value }, () => this.recalcCron());
457
- } else if (attr === 'hours') {
458
- this.setState({ hours: value }, () => this.recalcCron());
459
- } else if (attr === 'dates') {
460
- this.setState({ dates: value }, () => this.recalcCron());
461
- } else if (attr === 'months') {
462
- this.setState({ months: value }, () => this.recalcCron());
463
- } else if (attr === 'dow') {
464
- this.setState({ dow: value }, () => this.recalcCron());
465
- }
466
- }
467
-
468
- render(): React.JSX.Element {
469
- const tab = this.state.seconds !== false ? this.state.tab : this.state.tab + 1;
470
- return (
471
- <div style={styles.mainDiv}>
472
- <div style={{ paddingLeft: 8, width: '100%' }}>
473
- <TextField
474
- variant="standard"
475
- style={{ width: '100%' }}
476
- value={this.state.cron}
477
- disabled
478
- />
479
- </div>
480
- <div style={{ paddingLeft: 8, width: '100%', height: 60 }}>
481
- {ComplexCron.convertCronToText(this.state.cron, this.props.language || 'en')}
482
- </div>
483
- <FormControlLabel
484
- control={
485
- <Checkbox
486
- checked={!!this.state.seconds}
487
- onChange={e =>
488
- this.setState({ seconds: e.target.checked ? '*' : false }, () => this.recalcCron())
489
- }
490
- />
491
- }
492
- label={I18n.t('ra_use seconds')}
493
- />
494
- <AppBar
495
- position="static"
496
- sx={{ '&.MuiAppBar-root': styles.appBar }}
497
- color="secondary"
498
- >
499
- <Tabs
500
- value={this.state.tab}
501
- style={styles.appBar}
502
- color="secondary"
503
- onChange={(active, _tab) => this.setState({ tab: _tab })}
504
- >
505
- {this.state.seconds !== false && (
506
- <Tab
507
- id="sc_seconds"
508
- label={I18n.t('sc_seconds')}
509
- />
510
- )}
511
- <Tab
512
- id="minutes"
513
- label={I18n.t('sc_minutes')}
514
- />
515
- <Tab
516
- id="hours"
517
- label={I18n.t('sc_hours')}
518
- />
519
- <Tab
520
- id="dates"
521
- label={I18n.t('sc_dates')}
522
- />
523
- <Tab
524
- id="months"
525
- label={I18n.t('sc_months')}
526
- />
527
- <Tab
528
- id="dow"
529
- label={I18n.t('sc_dows')}
530
- />
531
- </Tabs>
532
- </AppBar>
533
- {tab === 0 && <div style={styles.tabContent}>{this.getPeriodsTab('seconds', 60)}</div>}
534
- {tab === 1 && <div style={styles.tabContent}>{this.getPeriodsTab('minutes', 60)}</div>}
535
- {tab === 2 && <div style={styles.tabContent}>{this.getPeriodsTab('hours', 24)}</div>}
536
- {tab === 3 && <div style={styles.tabContent}>{this.getPeriodsTab('dates', 31)}</div>}
537
- {tab === 4 && <div style={styles.tabContent}>{this.getPeriodsTab('months', 12)}</div>}
538
- {tab === 5 && <div style={styles.tabContent}>{this.getPeriodsTab('dow', 7)}</div>}
539
- </div>
540
- );
541
- }
542
- }
543
-
544
- export default ComplexCron;
1
+ import React, { Component } from 'react';
2
+
3
+ import { Checkbox, Button, MenuItem, Select, FormControlLabel, AppBar, Tabs, Tab, TextField } from '@mui/material';
4
+
5
+ import I18n from '../i18n';
6
+ import convertCronToText from './SimpleCron/cronText';
7
+
8
+ const styles: Record<string, React.CSSProperties> = {
9
+ mainDiv: {
10
+ width: '100%',
11
+ height: '100%',
12
+ },
13
+ periodSelect: {
14
+ // margin: '0 10px 60px 10px',
15
+ display: 'block',
16
+ width: 200,
17
+ },
18
+ slider: {
19
+ marginTop: 20,
20
+ display: 'block',
21
+ width: '100%',
22
+ },
23
+ tabContent: {
24
+ padding: 20,
25
+ height: 'calc(100% - 240px)',
26
+ overflow: 'auto',
27
+ },
28
+ numberButton: {
29
+ padding: 4,
30
+ minWidth: 40,
31
+ margin: 5,
32
+ },
33
+ numberButtonBreak: {
34
+ display: 'block',
35
+ },
36
+ appBar: {
37
+ color: 'white',
38
+ },
39
+ warning: {
40
+ marginLeft: 16,
41
+ color: 'red',
42
+ fontSize: 12,
43
+ },
44
+ };
45
+
46
+ const WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
47
+ const MONTHS = [
48
+ 'January',
49
+ 'February',
50
+ 'March',
51
+ 'April',
52
+ 'May',
53
+ 'June',
54
+ 'July',
55
+ 'August',
56
+ 'September',
57
+ 'October',
58
+ 'November',
59
+ 'December',
60
+ ];
61
+
62
+ // 5-7,9-11 => [5,6,7,9,10,11]
63
+ function convertMinusIntoArray(value: string | false | undefined, max: number): number[] {
64
+ const result: number[] = [];
65
+
66
+ if (value === '*') {
67
+ if (max === 24 || max === 60 || max === 7) {
68
+ for (let i = 0; i < max; i++) {
69
+ result.push(i);
70
+ }
71
+ } else {
72
+ for (let i = 1; i <= max; i++) {
73
+ result.push(i);
74
+ }
75
+ }
76
+ return result; // array with entries max
77
+ }
78
+
79
+ const parts = (value || '').toString().split(',');
80
+
81
+ for (let p = 0; p < parts.length; p++) {
82
+ if (!parts[p].trim().length) {
83
+ continue;
84
+ }
85
+ const items = parts[p].trim().split('-');
86
+ if (items.length > 1) {
87
+ const iMax = parseInt(items[1], 10);
88
+ for (let i = parseInt(items[0], 10); i <= iMax; i++) {
89
+ result.push(i);
90
+ }
91
+ } else {
92
+ result.push(parseInt(parts[p], 10));
93
+ }
94
+ }
95
+
96
+ result.sort();
97
+
98
+ // remove double entries
99
+ for (let p = result.length - 1; p >= 0; p--) {
100
+ if (result[p] === result[p + 1]) {
101
+ result.splice(p + 1, 1);
102
+ }
103
+ }
104
+
105
+ return result;
106
+ }
107
+
108
+ // [5,6,7,9,10,11] => 5-7,9-11
109
+ function convertArrayIntoMinus(value: number | number[], max: number): string {
110
+ if (typeof value !== 'object') {
111
+ value = [value];
112
+ }
113
+ if (value.length === max) {
114
+ return '*';
115
+ }
116
+ const newParts = [];
117
+ if (!value.length) {
118
+ return '-';
119
+ }
120
+ value = value.map(a => parseInt(a as any as string, 10));
121
+
122
+ value.sort((a, b) => a - b);
123
+
124
+ let start = value[0];
125
+ let end = value[0];
126
+ for (let p = 1; p < value.length; p++) {
127
+ if (value[p] - 1 !== parseInt(value[p - 1] as any as string, 10)) {
128
+ if (start === end) {
129
+ newParts.push(start);
130
+ } else if (end - 1 === start) {
131
+ newParts.push(`${start},${end}`);
132
+ } else {
133
+ newParts.push(`${start}-${end}`);
134
+ }
135
+ start = value[p];
136
+ }
137
+ end = value[p];
138
+ }
139
+
140
+ if (start === end) {
141
+ newParts.push(start);
142
+ } else if (end - 1 === start) {
143
+ newParts.push(`${start},${end}`);
144
+ } else {
145
+ newParts.push(`${start}-${end}`);
146
+ }
147
+
148
+ return newParts.join(',');
149
+ }
150
+
151
+ type CronNames = 'seconds' | 'minutes' | 'hours' | 'dates' | 'months' | 'dow';
152
+
153
+ interface CronProps {
154
+ seconds: string | false | null;
155
+ minutes: string | null;
156
+ hours: string | null;
157
+ dates: string | null;
158
+ months: string | null;
159
+ dow: string | null;
160
+ }
161
+
162
+ interface ComplexCronProps {
163
+ cronExpression: string;
164
+ onChange: (cron: string) => void;
165
+ language: ioBroker.Languages;
166
+ }
167
+
168
+ // type CronModes = 'every' | 'everyN' | 'specific';
169
+
170
+ interface ComplexCronState {
171
+ extended: boolean;
172
+ tab: number;
173
+ cron: string;
174
+ seconds?: string | false;
175
+ minutes?: string;
176
+ hours?: string;
177
+ dates?: string;
178
+ months?: string;
179
+ dow?: string;
180
+ modes: CronProps;
181
+ }
182
+
183
+ class ComplexCron extends Component<ComplexCronProps, ComplexCronState> {
184
+ constructor(props: ComplexCronProps) {
185
+ super(props);
186
+ let cron =
187
+ typeof this.props.cronExpression === 'string'
188
+ ? this.props.cronExpression.replace(/^["']/, '').replace(/["']\n?$/, '')
189
+ : '';
190
+ if (cron[0] === '{') {
191
+ cron = '';
192
+ }
193
+ const state = ComplexCron.cron2state(cron || '* * * * *');
194
+
195
+ this.state = {
196
+ extended: false,
197
+ tab: state.seconds !== false ? 1 : 0,
198
+ cron: ComplexCron.state2cron(state),
199
+ modes: {
200
+ seconds: null,
201
+ minutes: null,
202
+ hours: null,
203
+ dates: null,
204
+ months: null,
205
+ dow: null,
206
+ },
207
+ };
208
+ Object.assign(this.state, state);
209
+ if (this.state.cron !== this.props.cronExpression) {
210
+ setTimeout(() => this.props.onChange && this.props.onChange(this.state.cron), 100);
211
+ }
212
+ }
213
+
214
+ static cron2state(cron: string): CronProps {
215
+ cron = cron.replace(/['"]/g, '').trim();
216
+ const cronParts = cron.split(' ').map(p => p.trim());
217
+ let options: CronProps;
218
+
219
+ if (cronParts.length === 6) {
220
+ options = {
221
+ seconds: cronParts[0] || '*',
222
+ minutes: cronParts[1] || '*',
223
+ hours: cronParts[2] || '*',
224
+ dates: cronParts[3] || '*',
225
+ months: cronParts[4] || '*',
226
+ dow: cronParts[5] || '*',
227
+ };
228
+ } else {
229
+ options = {
230
+ seconds: false,
231
+ minutes: cronParts[0] || '*',
232
+ hours: cronParts[1] || '*',
233
+ dates: cronParts[2] || '*',
234
+ months: cronParts[3] || '*',
235
+ dow: cronParts[4] || '*',
236
+ };
237
+ }
238
+ return options;
239
+ }
240
+
241
+ static state2cron(state: ComplexCronState | CronProps): string {
242
+ let text = `${state.minutes} ${state.hours} ${state.dates} ${state.months} ${state.dow}`;
243
+ if (state.seconds !== false) {
244
+ text = `${state.seconds} ${text}`;
245
+ }
246
+ return text;
247
+ }
248
+
249
+ recalcCron(): void {
250
+ const cron = ComplexCron.state2cron(this.state);
251
+ if (cron !== this.state.cron) {
252
+ this.setState({ cron }, () => this.props.onChange && this.props.onChange(this.state.cron));
253
+ }
254
+ }
255
+
256
+ onToggle(i: boolean | number, type: CronNames, max: number): void {
257
+ if (i === true) {
258
+ this.setCronAttr(type, '*');
259
+ } else if (i === false) {
260
+ if (max === 60 || max === 24) {
261
+ this.setCronAttr(type, '0');
262
+ } else {
263
+ this.setCronAttr(type, '1');
264
+ }
265
+ } else {
266
+ const nums = convertMinusIntoArray(this.state[type], max);
267
+ const pos = nums.indexOf(i);
268
+ if (pos !== -1) {
269
+ nums.splice(pos, 1);
270
+ } else {
271
+ nums.push(i);
272
+ nums.sort();
273
+ }
274
+ this.setCronAttr(type, convertArrayIntoMinus(nums, max));
275
+ }
276
+ }
277
+
278
+ getDigitsSelector(type: CronNames, max: number): React.JSX.Element[] {
279
+ let values = [];
280
+ if (max === 7) {
281
+ values = [1, 2, 3, 4, 5, 6, 0];
282
+ } else if (max === 60 || max === 24) {
283
+ for (let i = 0; i < max; i++) {
284
+ values.push(i);
285
+ }
286
+ } else {
287
+ for (let i = 1; i <= max; i++) {
288
+ values.push(i);
289
+ }
290
+ }
291
+
292
+ const parts = convertMinusIntoArray(this.state[type], max);
293
+
294
+ return [
295
+ <Button
296
+ key="removeall"
297
+ variant="outlined"
298
+ style={styles.numberButton}
299
+ // style={{paddingBottom: 20}}
300
+ color="primary"
301
+ onClick={() => this.onToggle(false, type, max)}
302
+ >
303
+ {I18n.t('ra_Deselect all')}
304
+ </Button>,
305
+ <Button
306
+ key="addall"
307
+ variant="contained"
308
+ // style={{paddingBottom: 20}}
309
+ style={styles.numberButton}
310
+ color="secondary"
311
+ onClick={() => this.onToggle(true, type, max)}
312
+ >
313
+ {I18n.t('ra_Select all')}
314
+ </Button>,
315
+ <div key="all">
316
+ {values.map(i => [
317
+ (max === 7 && i === 4) ||
318
+ (max === 12 && i === 7) ||
319
+ (max === 31 && !((i - 1) % 10)) ||
320
+ (max === 60 && i && !(i % 10)) ||
321
+ (max === 24 && i && !(i % 6)) ? (
322
+ <div
323
+ key={`allInner${i}`}
324
+ style={{ width: '100%' }}
325
+ />
326
+ ) : null,
327
+ <Button
328
+ key={`_${i}`}
329
+ variant={parts.indexOf(i) !== -1 ? 'contained' : 'outlined'}
330
+ style={styles.numberButton}
331
+ color={parts.indexOf(i) !== -1 ? 'secondary' : 'primary'}
332
+ onClick={() => this.onToggle(i, type, max)}
333
+ >
334
+ {max === 7 ? I18n.t(WEEKDAYS[i]) : max === 12 ? MONTHS[i - 1] : i}
335
+ </Button>,
336
+ ])}
337
+ </div>,
338
+ ];
339
+ }
340
+
341
+ getPeriodsTab(type: CronNames, max: number): React.JSX.Element | null {
342
+ const value = this.state[type];
343
+ let every = value === '*';
344
+ let everyN = value === undefined || value === null ? false : value.toString().includes('/');
345
+ let select;
346
+ if (this.state.modes[type] === null) {
347
+ select = every ? 'every' : everyN ? 'everyN' : 'specific';
348
+ const modes = JSON.parse(JSON.stringify(this.state.modes));
349
+ modes[type] = select;
350
+ setTimeout(() => this.setState({ modes }, () => this.recalcCron()), 100);
351
+ return null;
352
+ }
353
+
354
+ every = this.state.modes[type] === 'every';
355
+ everyN = this.state.modes[type] === 'everyN';
356
+ select = this.state.modes[type];
357
+
358
+ let valueNumber = 1;
359
+ if (everyN && value) {
360
+ valueNumber = parseInt(value.replace('*/', ''), 10) || 1;
361
+ }
362
+
363
+ return (
364
+ <div>
365
+ <Select
366
+ variant="standard"
367
+ style={{ ...styles.periodSelect, verticalAlign: 'bottom' }}
368
+ value={select}
369
+ onChange={e => {
370
+ const modes = JSON.parse(JSON.stringify(this.state.modes));
371
+ modes[type] = e.target.value;
372
+ if (e.target.value === 'every') {
373
+ this.setCronAttr(type, '*', modes);
374
+ } else if (e.target.value === 'everyN') {
375
+ const num = parseInt((this.state[type] || '').toString().replace('*/', ''), 10) || 1;
376
+ this.setCronAttr(type, `*/${num}`, modes);
377
+ } else if (e.target.value === 'specific') {
378
+ let num = parseInt((this.state[type] || '').toString().split(',')[0], 10) || 0;
379
+ if (!num && (type === 'months' || type === 'dates')) {
380
+ num = 1;
381
+ }
382
+ this.setCronAttr(type, convertArrayIntoMinus(num, max), modes);
383
+ }
384
+ }}
385
+ >
386
+ <MenuItem
387
+ key="every"
388
+ value="every"
389
+ >
390
+ {I18n.t(`sc_every_${type}`)}
391
+ </MenuItem>
392
+ <MenuItem
393
+ key="everyN"
394
+ value="everyN"
395
+ >
396
+ {I18n.t(`sc_everyN_${type}`)}
397
+ </MenuItem>
398
+ <MenuItem
399
+ key="specific"
400
+ value="specific"
401
+ >
402
+ {I18n.t(`sc_specific_${type}`)}
403
+ </MenuItem>
404
+ </Select>
405
+ {/* everyN && false && <span>{value}</span> */}
406
+ {everyN && (
407
+ <TextField
408
+ variant="standard"
409
+ key="interval"
410
+ label={I18n.t(`sc_${type}`)}
411
+ value={valueNumber}
412
+ slotProps={{
413
+ htmlInput: {
414
+ min: 1,
415
+ max,
416
+ },
417
+ inputLabel: {
418
+ shrink: true,
419
+ },
420
+ }}
421
+ onChange={e => {
422
+ // @ts-expect-error is allowed
423
+ this.setState({ [type]: `*/${e.target.value}` }, () => this.recalcCron());
424
+ }}
425
+ type="number"
426
+ margin="normal"
427
+ />
428
+ )}
429
+ {!every && !everyN && this.getDigitsSelector(type, max)}
430
+ </div>
431
+ );
432
+ }
433
+
434
+ static convertCronToText(cron: string, lang: ioBroker.Languages): string {
435
+ if (cron.split(' ').includes('-')) {
436
+ return I18n.t('ra_Invalid CRON');
437
+ }
438
+ return convertCronToText(cron, lang);
439
+ }
440
+
441
+ setCronAttr(attr: CronNames, value: string, modes?: CronProps): void {
442
+ if (modes) {
443
+ if (attr === 'seconds') {
444
+ this.setState({ seconds: value, modes }, () => this.recalcCron());
445
+ } else if (attr === 'minutes') {
446
+ this.setState({ minutes: value, modes }, () => this.recalcCron());
447
+ } else if (attr === 'hours') {
448
+ this.setState({ hours: value, modes }, () => this.recalcCron());
449
+ } else if (attr === 'dates') {
450
+ this.setState({ dates: value, modes }, () => this.recalcCron());
451
+ } else if (attr === 'months') {
452
+ this.setState({ months: value, modes }, () => this.recalcCron());
453
+ } else if (attr === 'dow') {
454
+ this.setState({ dow: value, modes }, () => this.recalcCron());
455
+ } else {
456
+ this.setState({ modes }, () => this.recalcCron());
457
+ }
458
+ } else if (attr === 'seconds') {
459
+ this.setState({ seconds: value }, () => this.recalcCron());
460
+ } else if (attr === 'minutes') {
461
+ this.setState({ minutes: value }, () => this.recalcCron());
462
+ } else if (attr === 'hours') {
463
+ this.setState({ hours: value }, () => this.recalcCron());
464
+ } else if (attr === 'dates') {
465
+ this.setState({ dates: value }, () => this.recalcCron());
466
+ } else if (attr === 'months') {
467
+ this.setState({ months: value }, () => this.recalcCron());
468
+ } else if (attr === 'dow') {
469
+ this.setState({ dow: value }, () => this.recalcCron());
470
+ }
471
+ }
472
+
473
+ render(): React.JSX.Element {
474
+ const tab = this.state.seconds !== false ? this.state.tab : this.state.tab + 1;
475
+
476
+ // Detect if every minute or every second is activated
477
+ const everyMinute = this.state.minutes === '*' || this.state.minutes === '*/1';
478
+ const everySecond = this.state.seconds === '*' || this.state.seconds === '*/1';
479
+
480
+ return (
481
+ <div style={styles.mainDiv}>
482
+ <div style={{ paddingLeft: 8, width: '100%' }}>
483
+ <TextField
484
+ variant="standard"
485
+ style={{ width: '100%' }}
486
+ value={this.state.cron}
487
+ disabled
488
+ />
489
+ </div>
490
+ <div style={{ paddingLeft: 8, width: '100%', height: 60 }}>
491
+ {ComplexCron.convertCronToText(this.state.cron, this.props.language || 'en')}
492
+ <span style={styles.warning}>
493
+ {everySecond
494
+ ? I18n.t('ra_warning_every_second')
495
+ : everyMinute
496
+ ? I18n.t('ra_warning_every_minute')
497
+ : ''}
498
+ </span>
499
+ </div>
500
+ <FormControlLabel
501
+ control={
502
+ <Checkbox
503
+ checked={!!this.state.seconds}
504
+ onChange={e =>
505
+ this.setState({ seconds: e.target.checked ? '*' : false }, () => this.recalcCron())
506
+ }
507
+ />
508
+ }
509
+ label={I18n.t('ra_use seconds')}
510
+ />
511
+ <AppBar
512
+ position="static"
513
+ sx={{ '&.MuiAppBar-root': styles.appBar }}
514
+ color="secondary"
515
+ >
516
+ <Tabs
517
+ value={this.state.tab}
518
+ style={styles.appBar}
519
+ color="secondary"
520
+ onChange={(_active, _tab) => this.setState({ tab: _tab })}
521
+ >
522
+ {this.state.seconds !== false && (
523
+ <Tab
524
+ id="sc_seconds"
525
+ label={I18n.t('sc_seconds')}
526
+ />
527
+ )}
528
+ <Tab
529
+ id="minutes"
530
+ label={I18n.t('sc_minutes')}
531
+ />
532
+ <Tab
533
+ id="hours"
534
+ label={I18n.t('sc_hours')}
535
+ />
536
+ <Tab
537
+ id="dates"
538
+ label={I18n.t('sc_dates')}
539
+ />
540
+ <Tab
541
+ id="months"
542
+ label={I18n.t('sc_months')}
543
+ />
544
+ <Tab
545
+ id="dow"
546
+ label={I18n.t('sc_dows')}
547
+ />
548
+ </Tabs>
549
+ </AppBar>
550
+ {tab === 0 && <div style={styles.tabContent}>{this.getPeriodsTab('seconds', 60)}</div>}
551
+ {tab === 1 && <div style={styles.tabContent}>{this.getPeriodsTab('minutes', 60)}</div>}
552
+ {tab === 2 && <div style={styles.tabContent}>{this.getPeriodsTab('hours', 24)}</div>}
553
+ {tab === 3 && <div style={styles.tabContent}>{this.getPeriodsTab('dates', 31)}</div>}
554
+ {tab === 4 && <div style={styles.tabContent}>{this.getPeriodsTab('months', 12)}</div>}
555
+ {tab === 5 && <div style={styles.tabContent}>{this.getPeriodsTab('dow', 7)}</div>}
556
+ </div>
557
+ );
558
+ }
559
+ }
560
+
561
+ export default ComplexCron;