cron-human 1.1.2 → 1.1.4
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/dist/ui/app.js +68 -17
- package/dist/ui/components/HistoryPanel.d.ts +1 -0
- package/dist/ui/components/HistoryPanel.js +6 -2
- package/dist/ui/components/InputSection.d.ts +1 -0
- package/dist/ui/components/InputSection.js +27 -3
- package/dist/ui/components/PresetsModal.d.ts +7 -0
- package/dist/ui/components/PresetsModal.js +15 -0
- package/package.json +1 -1
package/dist/ui/app.js
CHANGED
|
@@ -5,6 +5,7 @@ import { InputSection } from './components/InputSection.js';
|
|
|
5
5
|
import { PreviewSection } from './components/PreviewSection.js';
|
|
6
6
|
import { OptionsPanel } from './components/OptionsPanel.js';
|
|
7
7
|
import { HistoryPanel } from './components/HistoryPanel.js';
|
|
8
|
+
import { PresetsModal } from './components/PresetsModal.js';
|
|
8
9
|
import clipboardy from 'clipboardy';
|
|
9
10
|
var FocusArea;
|
|
10
11
|
(function (FocusArea) {
|
|
@@ -15,7 +16,10 @@ var FocusArea;
|
|
|
15
16
|
export const App = () => {
|
|
16
17
|
const { exit } = useApp();
|
|
17
18
|
const [expression, setExpression] = useState('');
|
|
18
|
-
const [timezone] = useState(undefined);
|
|
19
|
+
const [timezone, setTimezone] = useState(undefined);
|
|
20
|
+
const [timezoneInput, setTimezoneInput] = useState('');
|
|
21
|
+
const [inputMode, setInputMode] = useState('cron');
|
|
22
|
+
const [showPresets, setShowPresets] = useState(false);
|
|
19
23
|
const [allowSeconds, setAllowSeconds] = useState(false);
|
|
20
24
|
const [focus, setFocus] = useState(FocusArea.Input);
|
|
21
25
|
const [history, setHistory] = useState([]);
|
|
@@ -32,6 +36,55 @@ export const App = () => {
|
|
|
32
36
|
exit();
|
|
33
37
|
return;
|
|
34
38
|
}
|
|
39
|
+
// Global Shortcuts (Paste, Reset, Toggles)
|
|
40
|
+
if (key.ctrl && input === 'v') {
|
|
41
|
+
if (focus === FocusArea.Input && !showPresets) {
|
|
42
|
+
clipboardy.read().then(text => {
|
|
43
|
+
if (text) {
|
|
44
|
+
if (inputMode === 'timezone') {
|
|
45
|
+
setTimezoneInput(text.trim());
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
setExpression(text.trim());
|
|
49
|
+
}
|
|
50
|
+
setNotification('Pasted from clipboard!');
|
|
51
|
+
}
|
|
52
|
+
}).catch(err => {
|
|
53
|
+
setNotification(`Paste failed: ${err.message}`);
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (key.ctrl && input === 'r') {
|
|
59
|
+
if (inputMode === 'timezone') {
|
|
60
|
+
setTimezoneInput('');
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
setExpression('');
|
|
64
|
+
}
|
|
65
|
+
setFocus(FocusArea.Input);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (key.ctrl && input === 't') {
|
|
69
|
+
setInputMode('timezone');
|
|
70
|
+
setTimezoneInput(timezone || '');
|
|
71
|
+
setFocus(FocusArea.Input);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (key.ctrl && input === 'p') {
|
|
75
|
+
setShowPresets(prev => !prev);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Mode Specific Handling
|
|
79
|
+
if (inputMode === 'timezone') {
|
|
80
|
+
if (key.escape) {
|
|
81
|
+
setInputMode('cron');
|
|
82
|
+
setFocus(FocusArea.Input);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return; // Let TextInput handle typing
|
|
86
|
+
}
|
|
87
|
+
// Cron Mode Handling
|
|
35
88
|
if (input === 'q' && !key.ctrl && focus !== FocusArea.Input) {
|
|
36
89
|
exit();
|
|
37
90
|
return;
|
|
@@ -44,23 +97,12 @@ export const App = () => {
|
|
|
44
97
|
return FocusArea.History;
|
|
45
98
|
return FocusArea.Input;
|
|
46
99
|
});
|
|
100
|
+
return;
|
|
47
101
|
}
|
|
48
|
-
if (key.ctrl && input === '
|
|
49
|
-
|
|
50
|
-
setFocus(FocusArea.Input);
|
|
51
|
-
}
|
|
52
|
-
if (key.ctrl && input === 'v') {
|
|
53
|
-
if (focus === FocusArea.Input) {
|
|
54
|
-
clipboardy.read().then(text => {
|
|
55
|
-
if (text) {
|
|
56
|
-
setExpression(text.trim());
|
|
57
|
-
setNotification('Pasted from clipboard!');
|
|
58
|
-
}
|
|
59
|
-
}).catch(err => {
|
|
60
|
-
setNotification(`Paste failed: ${err.message}`);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
102
|
+
if (key.ctrl && input === 's') { // Save? Or just implicit.
|
|
103
|
+
// Placeholder if needed
|
|
63
104
|
}
|
|
105
|
+
// Navigation etc handled by components or below
|
|
64
106
|
if (focus === FocusArea.Options) {
|
|
65
107
|
if (input === ' ') {
|
|
66
108
|
setAllowSeconds(prev => !prev);
|
|
@@ -92,6 +134,11 @@ export const App = () => {
|
|
|
92
134
|
}
|
|
93
135
|
});
|
|
94
136
|
const handleInputSubmit = (val) => {
|
|
137
|
+
if (inputMode === 'timezone') {
|
|
138
|
+
setTimezone(val.trim() || undefined);
|
|
139
|
+
setInputMode('cron');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
95
142
|
if (val.trim()) {
|
|
96
143
|
const last = history[0];
|
|
97
144
|
if (!last || last.expression !== val) {
|
|
@@ -99,5 +146,9 @@ export const App = () => {
|
|
|
99
146
|
}
|
|
100
147
|
}
|
|
101
148
|
};
|
|
102
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", height: "100%", children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "magenta", children: "Cron Human TUI" }), _jsx(Text, { children: " | " }), _jsx(Text, { dimColor: true, children: "Ctrl+C to Quit" })] }), notification && _jsx(Text, { color: "yellow", bold: true, children: notification })] }), _jsx(InputSection, { value: expression, onChange: setExpression, onSubmit: handleInputSubmit, isFocused: focus === FocusArea.Input }), _jsx(Box, { marginY: 1, children: _jsx(OptionsPanel, { isFocused: focus === FocusArea.Options, timezone: timezone || "Local", allowSeconds: allowSeconds }) }), _jsx(PreviewSection, { expression: expression, timezone: timezone, allowSeconds: allowSeconds }), _jsxs(Box, { marginTop: 1, children: [_jsx(HistoryPanel, { isFocused: focus === FocusArea.History, items: history.slice(0, 5), selectedIndex: historyIndex }), history.length > 5 && _jsxs(Text, { dimColor: true, children: ["... ", history.length - 5, " more"] })] })
|
|
149
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", height: "100%", children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "magenta", children: "Cron Human TUI" }), _jsx(Text, { children: " | " }), _jsx(Text, { dimColor: true, children: "Ctrl+C to Quit" })] }), notification && _jsx(Text, { color: "yellow", bold: true, children: notification })] }), _jsx(InputSection, { value: inputMode === 'timezone' ? timezoneInput : expression, onChange: inputMode === 'timezone' ? setTimezoneInput : setExpression, onSubmit: handleInputSubmit, isFocused: focus === FocusArea.Input && !showPresets, label: inputMode === 'timezone' ? "Enter Timezone (e.g. UTC, America/New_York) [Esc to cancel]:" : "Cron Expression:" }), _jsx(Box, { marginY: 1, children: _jsx(OptionsPanel, { isFocused: focus === FocusArea.Options, timezone: timezone || "Local", allowSeconds: allowSeconds }) }), _jsx(PreviewSection, { expression: expression, timezone: timezone, allowSeconds: allowSeconds }), _jsxs(Box, { marginTop: 1, children: [_jsx(HistoryPanel, { isFocused: focus === FocusArea.History, items: history.slice(Math.max(0, historyIndex - 2), Math.max(0, historyIndex - 2) + 5), selectedIndex: historyIndex, startIndex: Math.max(0, historyIndex - 2) }), history.length > 5 && _jsxs(Text, { dimColor: true, children: ["... ", history.length - 5, " more"] })] }), showPresets && (_jsx(Box, { position: "absolute", minHeight: 10, minWidth: 50, marginTop: 2, marginLeft: 5, children: _jsx(PresetsModal, { onSelect: (val) => {
|
|
150
|
+
setExpression(val);
|
|
151
|
+
setShowPresets(false);
|
|
152
|
+
setFocus(FocusArea.Input);
|
|
153
|
+
}, onCancel: () => setShowPresets(false) }) }))] }));
|
|
103
154
|
};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
export const HistoryPanel = ({ isFocused, items, selectedIndex }) => {
|
|
3
|
+
export const HistoryPanel = ({ isFocused, items, selectedIndex, startIndex }) => {
|
|
4
4
|
if (items.length === 0) {
|
|
5
5
|
return (_jsxs(Box, { borderStyle: "round", borderColor: isFocused ? "green" : "gray", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "History:" }), _jsx(Text, { dimColor: true, children: "No history yet. Press Enter to save successful runs." })] }));
|
|
6
6
|
}
|
|
7
|
-
return (_jsxs(Box, { borderStyle: "round", borderColor: isFocused ? "green" : "gray", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "History (Up/Down to navigate, Enter to load, 'c' to copy):" }), items.map((item, index) =>
|
|
7
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: isFocused ? "green" : "gray", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "History (Up/Down to navigate, Enter to load, 'c' to copy):" }), items.map((item, index) => {
|
|
8
|
+
const globalIndex = startIndex + index;
|
|
9
|
+
const isSelected = globalIndex === selectedIndex;
|
|
10
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? "cyan" : "white", children: [isSelected ? "> " : " ", item.expression] }) }, index));
|
|
11
|
+
})] }));
|
|
8
12
|
};
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
4
|
import TextInput from 'ink-text-input';
|
|
4
|
-
export const InputSection = ({ value, onChange, onSubmit, isFocused }) => {
|
|
5
|
-
|
|
5
|
+
export const InputSection = ({ value, onChange, onSubmit, isFocused, label = "Cron Expression:" }) => {
|
|
6
|
+
// FIX: ink-text-input adds characters even if Ctrl is held (except Ctrl+C).
|
|
7
|
+
// We safeguard against this by tracking the Ctrl key state.
|
|
8
|
+
const ctrlHeld = useRef(false);
|
|
9
|
+
useInput((_input, key) => {
|
|
10
|
+
ctrlHeld.current = key.ctrl;
|
|
11
|
+
}, { isActive: true }); // Always track, or at least when focused
|
|
12
|
+
// To attempt "Context" feature simply:
|
|
13
|
+
const parts = value.trim().split(/\s+/);
|
|
14
|
+
let helpText = "";
|
|
15
|
+
if (parts.length > 5)
|
|
16
|
+
helpText = "Fields: Sec Min Hour Day Month Weekday";
|
|
17
|
+
else
|
|
18
|
+
helpText = "Fields: Min Hour Day Month Weekday";
|
|
19
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: isFocused ? "green" : "gray", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: isFocused ? "green" : "white", children: label }), _jsx(Box, { children: _jsx(TextInput, { value: value, onChange: (val) => {
|
|
20
|
+
// FIX: Defer the update to allow useInput (below/parent) to update ctrlHeld state.
|
|
21
|
+
// ink events bubble child->parent. TextInput (child) fires onChange.
|
|
22
|
+
// Then InputSection (parent) useInput fires.
|
|
23
|
+
// By deferring, we check ctrlHeld *after* useInput has run for this event.
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
if (!ctrlHeld.current) {
|
|
26
|
+
onChange(val);
|
|
27
|
+
}
|
|
28
|
+
}, 0);
|
|
29
|
+
}, onSubmit: onSubmit, focus: isFocused, placeholder: "* * * * *" }) }), isFocused && label.includes("Cron") && (_jsx(Box, { marginTop: 0, children: _jsx(Text, { dimColor: true, children: helpText }) }))] }));
|
|
6
30
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
const presets = [
|
|
5
|
+
{ label: 'Every Minute (* * * * *)', value: '* * * * *' },
|
|
6
|
+
{ label: 'Every Hour (0 * * * *)', value: '0 * * * *' },
|
|
7
|
+
{ label: 'Every Day (0 0 * * *)', value: '0 0 * * *' },
|
|
8
|
+
{ label: 'Every Weekday (0 0 * * 1-5)', value: '0 0 * * 1-5' },
|
|
9
|
+
{ label: 'Every Weekend (0 0 * * 0,6)', value: '0 0 * * 0,6' },
|
|
10
|
+
{ label: 'First of Month (0 0 1 * *)', value: '0 0 1 * *' },
|
|
11
|
+
{ label: 'Start of Year (0 0 1 1 *)', value: '0 0 1 1 *' },
|
|
12
|
+
];
|
|
13
|
+
export const PresetsModal = ({ onSelect, onCancel }) => {
|
|
14
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: "cyan", padding: 1, width: 50, children: [_jsx(Text, { bold: true, children: "Select a Preset (Esc to cancel):" }), _jsx(Box, { marginY: 1, children: _jsx(SelectInput, { items: presets, onSelect: (item) => onSelect(item.value), isFocused: true }) }), _jsx(Text, { dimColor: true, children: "Use Arrow keys to select, Enter to apply" })] }));
|
|
15
|
+
};
|